介绍一下Basys开发板:

Basys2 FPGA开发板是一个电路设计实现平台,任何人都可以通过它来搭建一个真正的数字电路。Basys2是围绕着一个Spartan-3E FPGA芯片和一个Atmel AT90USB USB控制器搭建的,它提供了完整、随时可以使用的硬件平台,并且它适合于从基本逻辑器件到复杂控制器件的各种主机电路.Basys2开发板兼容所有版本的Xilinx ISE工具,其中也包括免费的WebPack版本。Basys2附带一个用于供电和编程的USB下载线,所以就不需要其他供电器件或编程下载线。

FPGA课设-基于Xilinx Basys2开发板的除法器设计-LMLPHP

此次课设实现的功能:

设计一个除法器,能在Basys2开发板上实际运行。

被除数为16位,除数为8位,被除数和除数都用按键输入,结果用数码管显示,设置一个使能开关,开关朝上拨时才进行运算。由于数码管和按键等资源数量较少,因此可以考虑采取下面的方案实现。

LD2  LD1  LD0指示状态,000是起始状态,001用于输入被除数高8位,010用于输入被除数低8位,011用于输入除数(8位),100用于显示结果,101用于显示被除数和除数

btn1和btn0和数码管配合,用于改变准备输入的数据

btn2用于将数码管显示的数据输入到相应的地方,同时切换状态。btn3 的功能也是切换状态,如果用btn3切换状态,则不改变原来的数据。

sw6如果为1表示允许进行除法运算,为0则表示不允许。

因为LD2-LD0为100和101时需要显示6位16进制数,而数码管只有4个,所以用sw7进行切换。LD2-LD0为100时,sw7为0时显示商,为1时显示余数,LD2-LD0为101时,sw7为0时显示被除数,为1时显示除数。

设计一个除法器,能在Basys2开发板上实际运行。

被除数为16位,除数为8位,被除数和除数都用按键输入,结果用数码管显示,设置一个使能开关,开关朝上拨时才进行运算。由于数码管和按键等资源数量较少,因此可以考虑采取下面的方案实现。

使用2个开关决定状态,例如SW1和SW0,SW1-SW0为00时用于输入被除数,通过4个按键输入4位16进制数,输入的数通过数码管显示;01时用于输入除数,通过2个按键输入2位16进制数,输入的数通过数码管显示;10时显示商;11时显示余数。

上代码:

顶层模块chufaqi.v:

 module chufaqi(reset,en,clk,sw1,sw0,btn3,btn2,btn1,btn0,an0,an1,an2,an3,
dp,cg,cf,ce,cd,cc,cb,ca); //除法器顶层模块chufaqi,端口名列表
//端口的申明
input reset,clk,sw1,sw0,btn3,btn2,btn1,btn0;
output an0,an1,an2,an3,dp,cg,cf,ce,cd,cc,cb,ca;
input en; //四个待显示的数据
reg[:] disp_data0,disp_data1,disp_data2,disp_data3;
//16位被除数
wire[:] dividend;
//8位除数
wire[:] divisor;
//16位商
wire[:] res;
//8位余数
wire[:] rm;
//输入被除数时存放按键状态的寄存器
reg dividend_key0,dividend_key1,dividend_key2,dividend_key3;
//除数输入的时候存放按键状态寄存器
reg divisor_key0,divisor_key1; always@(*) //任何被赋值变量发生变化时执行
begin
if({sw1,sw0}=='b00) //开关sw1和sw0关闭
begin
dividend_key0<=btn0; //被除数的按键输入
dividend_key1<=btn1;
dividend_key2<=btn2;
dividend_key3<=btn3;
end
else
begin
dividend_key0<='b0; //当开关sw1和sw0不是都关闭的时候,把所有的按键状态都清0
dividend_key1<='b0;
dividend_key2<='b0;
dividend_key3<='b0;
end
end always@(*)
begin
if({sw1,sw0}=='b01) //开关sw1关闭,并且sw0打开的时候,进行除数的输入
begin
divisor_key0<=btn0; //除数的按键输入
divisor_key1<=btn1;
end
else
begin
divisor_key0<='b0; //当不是开关sw1关闭,并且sw0打开的时候,把所有的按键状态都清0
divisor_key1<='b0;
end
end always@(*) //任何被赋值变量发生变化时都会执行
begin
case({sw1,sw0}) //根据sw1和sw0的状态来选择不同的数进行显示
'b00: //sw1=0,sw0=0的时候,显示被除数
begin
disp_data0<=dividend[:]; //把被除数的0到3位放在寄存器disp_data0中,在数码管模块中显示
disp_data1<=dividend[:]; //把被除数的4到7位放在寄存器disp_data1中,在数码管模块中显示
disp_data2<=dividend[:]; //把被除数的8到11位放在寄存器disp_data2中,在数码管模块中显示
disp_data3<=dividend[:]; //把被除数的12到15位放在寄存器disp_data3中,在数码管模块中显示
end
'b01: //sw1=0,sw0=1的时候,显示除数
begin
disp_data0<=divisor[:]; //把除数的0到3位放在寄存器disp_data0中,在数码管模块中显示
disp_data1<=divisor[:]; //把被除数的4到7位放在寄存器disp_data1中,在数码管模块中显示
disp_data2<='b0000; //把disp_data2和disp_data3赋值为0,即把左边的两个数码管均显示为0
disp_data3<='b0000;
end
'b10: //sw1=1,sw0=0的时候,显示商
begin
disp_data0<=res[:]; //把商的0到3位放在寄存器disp_data0中,在数码管模块中显示
disp_data1<=res[:]; //把商的4到7位放在寄存器disp_data1中,在数码管模块中显示
disp_data2<=res[:]; //把商的8到11位放在寄存器disp_data2中,在数码管模块中显示
disp_data3<=res[:]; //把商的12到15位放在寄存器disp_data3中,在数码管模块中显示
end
'b11: //sw1=1,sw0=1的时候,显示余数
begin
disp_data0<=rm[:]; //把余数的0到3位放在寄存器disp_data0中,在数码管模块中显示
disp_data1<=rm[:]; //把余数的4到8位放在寄存器disp_data1中,在数码管模块中显示
disp_data2<='b0000; //把disp_data2和disp_data3赋值为0,即把左边的两个数码管均显示为0
disp_data3<='b0000;
end
endcase
end //调用除法器模块 division u1(.reset(reset),.en(en),.clk(clk),.num(dividend),.den(divisor),.res(res),.rm(rm)); //调用数码管显示模块 shumaguan u2(.clk(clk),.reset(reset),.datain0(disp_data0),.datain1(disp_data1),
.datain2(disp_data2),.datain3(disp_data3),
.an0(an0),.an1(an1),.an2(an2),.an3(an3),
.dp(dp),.cg(cg),.cf(cf),.ce(ce),.cd(cd),.cc(cc),.cb(cb),.ca(ca)); //下面调用按键计数模块,分别对被除数的输入和除数的输入的时候的按键次数进行计数
//输入被除数的第一位数,即dividend[3:0]
key_count u3(.reset(reset),.clk(clk),.key(dividend_key0),.q(dividend[:]));
//输入被除数的第二位数,即dividend[7:4]
key_count u4(.reset(reset),.clk(clk),.key(dividend_key1),.q(dividend[:]));
//输入被除数的第三位数,即dividend[11:8]
key_count u5(.reset(reset),.clk(clk),.key(dividend_key2),.q(dividend[:]));
//输入被除数的第四位数,即dividend[15:12]
key_count u6(.reset(reset),.clk(clk),.key(dividend_key3),.q(dividend[:]));
//输入除数的第一位数,即divisor[3:0]
key_count u7(.reset(reset),.clk(clk),.key(divisor_key0),.q(divisor[:]));
//输入除数的第二位数,即divisor[7:4]
key_count u8(.reset(reset),.clk(clk),.key(divisor_key1),.q(divisor[:])); endmodule

除法器模块division.v

 module division(reset,en,clk,num,den,res,rm);  //除法器模块
//端口申明
input reset,en,clk;
input[:] num; //被除数
input[:] den; //除数
output[:] res; //商
output[:] rm; //余数 reg[:] res;
reg[:] rm; //用tbuf寄存器来存放被除数,用dbuf寄存器来存放除数
(*keep="true"*)reg[:] dbuf='d0; //(*keep="true"*)保证在综合优化的时候不会被剪切掉
reg[:] tbuf='d0; //tbuf初始化 reg[:] state; //定义状态寄存器 always@(posedge reset or posedge clk) //在复位信号的上升沿或者时钟信号的上升沿执行
begin
if(reset) //复位
begin
res<='d0; //商清零
rm<='b0000_0000; //余数清零
state<='b00000; //状态清零
end
else
begin
if(en) //使能信号,出发允许信号
begin
case(state) //状态机,根据state的不同状态进行不同的操作,这里用状态机进行16次的循环
:
begin
tbuf[:]<= 'b0000_0000; //tbuf的高8位全补0
tbuf[:] <= num; //tbuf的低16位放被除数
dbuf<={'b0,den}; //dbuf最高位放0,低8位放除数
res<=tbuf[:]; //最终结果是:tbuf的低16位就是商
rm<=tbuf[:]; //tbuf的高8位就是余数
state<=state+'b1; //状态变量增加1
end
default: //当state!=5'b00000时执行default
begin
if(tbuf[:]>=dbuf[:]) //判断比较tbuf高9位和dbuf的大小,相当于对被除数的最高的一位进行除法。满足条件执行下面的代码。
begin
tbuf[:]<=tbuf[:]-dbuf[:]; //对被除数此时的位数减去除数得到这一步的余数,再把余数存放到tbuf的高8位。
tbuf[:]<={tbuf[:],'b1}; //把等待除法操作的被除数的最高位放在tnuf的第15为上,之后一直进行上述的操作。
end
else tbuf <= (tbuf << ); //另一种写法:tbuf<={tbuf[22:0],1'b0}; 即将tbuf向左移一位。就是当tbuf[23:15]<dbuf[8:0]时,此时商是0,余数为此时的被除数的位数,把商放在tbuf最后,即向左移一位。 if(state!='b10000) //对未使用的state不断加1,回到0000-1111的状态,跳过未使用的state的状态
state<=state+'b1;
else
state<='b00000; //回到state=5'b00000的状态,显示商和余数
end
endcase
end
end
end endmodule
//所以总的来说,此除法器的原理:就是移位 加上 减法。

按键计数模块key_count.v

 module key_count(reset,clk,key,q); //按键计数模块

 //端口的申明
input reset,clk,key;
output[:] q; //寄存器q
reg[:] q; //保持按键持续状态的寄存器
reg key_last; //定按键次数增加的标志位rise_get
reg rise_get='b0; always @(posedge reset or posedge clk) //时钟上升沿或者复位信号的上升沿执行
begin
if(reset) //复位
key_last<='b0; //清零
else
key_last<=key; //否则把按键的状态存放在寄存器key_last中
end always @(posedge reset or posedge clk) //时钟上升沿或者复位信号的上升沿执行
begin
if(reset) //检测到reset为高电平的时候,即复位的时候
rise_get<='b0; //将rise_get清零
else if((!key_last)&&key)
rise_get<='b1; //当key_last=0并且key=1的时候,令按键增加标志rise_get=1。这行代码可以防止按键一直按下,按键一直按下的时候不会对按键计数
else //否则把rise_get清零
rise_get<='b0;
end always @(posedge reset or posedge clk) //时钟上升沿或者复位信号的上升沿执行
begin
if(reset)
q<='b0000; //复位清零
else if(rise_get) //如果按键增加标志rise_get=1的时候按键次数加一
q<=q+'b1;
end endmodule

数码管显示模块shumaguan.v

 module shumaguan(clk,reset,datain0,datain1,datain2,datain3,an0,an1,an2,an3,dp,cg,cf,ce,cd,cc,cb,ca); //数码管显示模块
//端口的申明
input clk,reset;
input[:] datain0,datain1,datain2,datain3;
output an0,an1,an2,an3;
output dp,cg,cf,ce,cd,cc,cb,ca;
reg an0,an1,an2,an3; //输出数码管的选通信号
reg cg,cf,ce,cd,cc,cb,ca; //输出数码管的“七段” wire dp; //数码管的“点” reg div1000,div100;
reg[:] div1000_count; //定义一个10位的计数器
reg[:] div100_count; //定义一个7位的计数器
reg[:] state; //四种状态
reg[:] data; //0~F 需要显示的值,某一位数码管显示的数字 //计数器用来分频,用于显示数码管
always @(posedge clk or posedge reset) //在时钟的上升沿或者复位信号的上升沿的时候执行
begin
if(reset) //复位的时候
begin
div1000_count<='d0; //把计数器清零
div1000<='b0; //把计数的标志位清零
end
else if(div1000_count=='d999) //当计数器计数到999的时候
begin
div1000_count<='d0; //div1000_count范围0~999 上一个上升沿div1000计数达到999 这个上升沿返回0
div1000<='b1; //计数满1000 输出标志1
end
else
begin
div1000_count<=div1000_count+'b1; //当计数器div1000_count没有达到999的时候,每次时钟上升沿的时候计数器加一
div1000<='b0; //并且保证此时的计数“满”的标志为0
end
end always @(posedge clk or posedge reset) //在时钟的上升沿或者复位信号的上升沿的时候执行
begin
if(reset) //复位
begin
div100_count<='b000_0000; //将计数器div100_count清零
div100<='b0; //将计数器的标志位div100清零
end
else //未复位的时候
begin
if(div1000) //当计数器div1000_count满999的时候,标志位div1000会被置1
begin
if(div100_count=='d99)
begin
div100_count<='b000_0000; //如果计数器div100_count计数满99的时候将计数器div100_count清零
//div100<=1'b1;
end
else
begin
div100_count<=div100_count+'b1; //如果计数器div100_count计数不满99的时候将计数器div100_count加一
//div100<=1'b0;
end
end
if((div1000)&&(div100_count=='d99)) //当计数器div100_count满99并且计数器div1000_count满999的时候将标志位div100置1
div100<='b1;
else
div100<='b0; end
end always @(posedge clk or posedge reset) //在时钟上升沿或者复位的上升沿的时候执行
begin
if(reset) //复位
begin
state<='b00; //状态复位为00
data<='b0000; //显示的数据清零
{an3,an2,an1,an0}<='b1111; //所有的数码管都不选通
end
else
begin
case(state) //根据state不同状态来选通不通的数码管显示数据
'b00:
begin
data<=datain0; //显示datain0的值
{an3,an2,an1,an0}<='b1110; //共阳 an0亮 其余不亮
end
'b01:
begin
data<=datain1;
{an3,an2,an1,an0}<='b1101;
end
'b10:
begin
data<=datain2;
{an3,an2,an1,an0}<='b1011;
end
'b11:
begin
data<=datain3;
{an3,an2,an1,an0}<='b0111;
end
endcase if(div100)
begin
state<=state+'b1; //每计数100000次 state加一 下一个数码管亮 数码管扫描显示
end
end
end always @(data) //always当data发生变化的时候执行
begin
case(data) //根据显示的数据来译码驱动数码管显示
'b0000:{cg,cf,ce,cd,cc,cb,ca}<=7'b1000000; //
'b0001:{cg,cf,ce,cd,cc,cb,ca}<=7'b1111001; //
'b0010:{cg,cf,ce,cd,cc,cb,ca}<=7'b0100100; //
'b0011:{cg,cf,ce,cd,cc,cb,ca}<=7'b0110000; //
'b0100:{cg,cf,ce,cd,cc,cb,ca}<=7'b0011001; //
'b0101:{cg,cf,ce,cd,cc,cb,ca}<=7'b0010010; //
'b0110:{cg,cf,ce,cd,cc,cb,ca}<=7'b0000010; //
'b0111:{cg,cf,ce,cd,cc,cb,ca}<=7'b1111000; //
'b1000:{cg,cf,ce,cd,cc,cb,ca}<=7'b0000000; //
'b1001:{cg,cf,ce,cd,cc,cb,ca}<=7'b0010000; //
'b1010:{cg,cf,ce,cd,cc,cb,ca}<=7'b0001000; //a
'b1011:{cg,cf,ce,cd,cc,cb,ca}<=7'b0000011; //b
'b1100:{cg,cf,ce,cd,cc,cb,ca}<=7'b1000110; //c
'b1101:{cg,cf,ce,cd,cc,cb,ca}<=7'b0100001; //d
'b1110:{cg,cf,ce,cd,cc,cb,ca}<=7'b0000110; //e
'b1111:{cg,cf,ce,cd,cc,cb,ca}<=7'b0001110; //f
endcase
end assign dp='b1; //dp(每个数码管的“点”)一直灭 endmodule

端口约束文件chufaqi.ucf

#Inputs
NET "clk" LOC="B8";
#NET "clk" CLOCK_DEDICATED_ROUTE = FALSE;
NET "reset" LOC="F3"; #SW5是复位信号开关
NET "en" LOC="G3"; #SW4是使能信号开关 NET "sw1" LOC="B4"; #SW3和sw2输入被除数和除数
NET "sw0" LOC="K3"; NET "btn3" LOC="A7"; #BTN3
NET "btn2" LOC="M4"; #BTN2
NET "btn1" LOC="C11"; #BTN1
NET "btn0" LOC="G12"; #BTN0 #Outputs NET "an0" LOC="F12";
NET "an1" LOC="J12";
NET "an2" LOC="M13";
NET "an3" LOC="K14"; NET "dp" LOC="N13";
NET "cg" LOC="M12";
NET "cf" LOC="L13";
NET "ce" LOC="P12";
NET "cd" LOC="N11";
NET "cc" LOC="N14";
NET "cb" LOC="H12";
NET "ca" LOC="L14";
05-11 20:41