写在前面的话
我大学本科学的是测控专业,2012年考取首都师范大学物理系研究生。我从未学习过数字电路设计,对FPGA和Verilog语言没有任何概念,更没有设计数字电路系统的基础和经验,也从未自己动手装配和完成过一台能实际运行的电子系统。但我从小就对电子设计有浓厚的兴趣。为什么小小的计算器按几下就能完成非常复杂的数学计算,一直困惑着我,激起我年轻的好奇心。大学四年里,虽然学习过“数字电路”和“模拟电路”课程,考试成绩也很不错,但对我而言,计算器是如何设计的,仍旧是一头雾水。
听同学们说,如果掌握了FPGA设计,这个谜就能找到答案。我用关键字“FPGA培训”在百度搜索,发现一个公司正在开设FPGA就业培训(100天)班,也知道这个班由北京航空航天大学的夏宇闻教授亲自讲授和管理。于是下定决心抽出3个月时间,认真学习一下FPGA。经过100天的学习和练习,我初步掌握了如何用FPGA芯片设计和搭建复杂数字系统。现在我有充分的信心,只要设计需求明确,我完全有能力独立设计并完成一个较复杂的数字系统,并能可靠地完成预先设定的数据 处理任务。这个阶段的学习给了我很多启发,也增强了我的信心,很想把自己的感受和学习心得编写成小册子与大家分享。我的想法得到夏宇闻教授的支持。于是我把学习期间的心路历程和学到的知识、经验略加整理,以日记的形式写出来,与大家分享,希望能给打算学习Verilog和FPGA设计的初学者一些帮助和启发,起到抛砖引玉的作用。
本书使用的硬件为至芯科技的四代开发板、Altera CycloneⅣ的芯片,软件为QuartusⅡ13.0 sp1。
——作者
1
设计需求讲解到目前为止,我设计的计算器已经能实现基本的运算功能,而且考虑了组合逻辑除法器的复杂度,对除法器的RTL代码做了相应的修改,使得综合后占用的逻辑资源有显著减少,唯一美中不足的就是尚不能显示负数。也就是当两个数相减,若被减数小于减数时,会出现一个很大的数,比如在做十进制运算“1 - 2”时,希望得到的计算结果是“-1”,而不是一个很长的数。
实际上该运算结果并没有错,只是我一直都是在用无符号数做运算。无符号数做减法时,若被减数小于减数,就会产生很大的数字。例如,十进制运算“1 – 2”,若只用4位二进制数进行运算,运算结果只允许保留4位,则可以表示为:(0001)2-(0010)2 = (1111)2 。减法运算的结果等于十进制的15。而十进制运算:15 + 2 = 17,如果用4比特二进制数做加法,加法运算的结果应该等于 (17)10 ,至少用5比特二进制数才能表示(17)10:(1111)2+(0010)2=(10001)2,(10001)2是十进制数17的5位二进制表示,若只允许保留(10001)2的后4位,则15 + 2的加法运算结果等于(0001)2= 1,这与 -1 + 2 = 1相当,可见4位有符号的二进制数(1111)2与有符号十进制 -1是可以建立等价关系的。所以只要对有符号二进制数的位数有明确的定义,利用上述表示负数的原理,解决计算中出现负数的问题也就并不困难。
1 – 2 = 15显然不是正确的运算结果。但是如果我们认为(15)10是一个4比特有符号二进制数的话,则(1111)2表示的就是(-1)10,换言之(1111)2是“-1”的补码表示,这就是我们想要得到的数了。
所以我要做的工作只是明确定义计算器内部参与运算数的比特位数,并告诉显示模块运算结果的绝对值是多少,以及该运算结果(BCD码)的符号是正的还是负的就可以了,显示模块根据这两个信息,就能够正确地显示出该数的正/负号和绝对值。
2
二进制数表示法计算器在运算时,若表示十进制数据值正/负和大小的符号位和数值位同时参与运算,则很有可能产生错误的运算结果;若把正/负符号问题分开考虑,又会增加运算器件的实现难度。因此,为了使计算器能够方便地对十进制数值进行各种算术逻辑运算,必须对十进制(BCD)数据进行二进制编码处理。所谓编码是采用规定位数的基本符号(如0和1),按照一定的组合原则,来表示各种复杂信息的技术。编码的优劣直接影响到计算器处理信息的速度。数值型数据的常用编码方法包括:原码、反码、补码。
三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同。
原码:原码表示法在数值前面增加了一位符号位(即最高位为符号位),其余位表示数值的大小。原码看起来简单直观,例如我们用4位二进制表示一个数,+1的原码为0001,-1的原码就是1001。显然,按原码的编码规则,零有两种表示形式(0000、1000)。
反码:机器数的反码可由原码得到。如果机器数是正数,则该机器数的反码与原码一样;如果机器数是负数,则该机器数的反码是对它的原码(符号位除外)各位取反而得到的。例如我们用4位二进制表示一个数,+1的反码为0001,-1的反码就是1110。显然,按反码的编码规则,零也有两种表示形式(0000、1111)。
补码:正数的补码等于原码,负数的补码等于反码末位加1。例如我们用4位二进制表示一个数,+1的补码为0001,-1的补码就是1111。显然,按补码的编码规则,零有唯一的表示形式(0000)。
补码的概念来源于数学上的“模”和补数。例如,将钟表的时针顺时针拨快5小时和逆时针拨慢7小时,最后指示的位置相同,则称5和–7互为模12情况下的补数。计算机中机器数受机器字长限制,所以是有限字长的数字系统。对于整数来说,机器字长为n位(含符号位),模是2n;对于有符号纯小数来说,模是2。
采用补码进行加减运算十分方便。通过对负数的编码处理,允许符号位和数值一起参与运算,可以把减法运算转化为加法运算。不论求和求差,也不论操作数为正为负,运算时一律只做加法,从而大大简化运算器的设计,加快了运算速度。所以机器系统中,数值一律用补码来表示和存储。
既然电路中使用补码的编码形式,而显示模块不认识补码,那么我们就把补码转换成原码再去显示即可,所以今天我们做的核心模块,就是补码转原码模块。
3
补码原码转换模块
将补码转换为原码很容易,正数的原码和补码相同,负数的原码为补码的取反加一(不考虑符号位)。再举-1的例子,电路中保存的数是1111,由于最高位(符号位)为1,表示负数,所以进行取反得到1000,再加一为1001,就是原码的-1。
加入了负数之后,计算器的左边第一个数码管将显示符号,也就是说,我们需要对数据位宽进行修改,因为我们要计算的二进制数从二十位[19:0]变成了十七位[16:0]外加一个符号位[17:0]。
1、补码转原码模块的可综合代码
补码转原码模块的可综合代码如下:
module comp2true0(datain,dataout);
input [17:0] datain;
output [17:0] dataout;
//判断符号位,负数取反加一,正数不变
assign dataout = datain[17] ? {1'b1,((~datain[16:0]) + 1)} : datain;
endmodule
2、转到ModelSim仿真工具进行测试
这段代码比较简单,写一些容易看出来的数测试一下:
`timescale 1ns/1ns
module comp2true_tb0;
reg [17:0] datain;
wire [17:0] dataout;
comp2true0 u1(.datain(datain),.dataout(dataout));
initial
begin
datain=0;
#1000 datain = 18'b00_0000_0000_0001_0000; //16
#1000 datain = 18'b11_1111_1111_1000_0000; //-128
#1000 datain = 18'b00_0000_0000_1000_0000; //128
#1000 datain = 18'b11_1111_1111_1111_0000; //-16
#1000 $stop;
end
endmodule
写好后保存,分析综合一下检查是否有语法错误,通过后进行Simulation的设置,运行RTL仿真。仿真波形如图10-1所示。
图10-1 仿真结果1
正数不变,负数取反加一得到原码,没有问题。
3、原码转补码模块的可综合代码序
原码转补码模块的可综合代码如下:
`timescale 1ns/1ns
module comp2true_tb0;
reg [17:0] datain;
wire [17:0] dataout;
comp2true0 u1(.datain(datain),.dataout(dataout));
initial
begin
datain=0;
#1000 datain = 18'b00_0000_0000_0001_0000; //16
#1000 datain = 18'b11_1111_1111_1000_0000; //-128
#1000 datain = 18'b00_0000_0000_1000_0000; //128
#1000 datain = 18'b11_1111_1111_1111_0000; //-16
#1000 $stop;
end
endmodulemodule true2comp0(datain,dataout);
input [17:0] datain;
output [17:0] dataout;
//判断符号位,负数减一取反,正数不变
assign dataout = datain[17] ? {1'b1,(~(datain[16:0] - 1))} : datain;
endmodule
4、转到ModelSim仿真工具进行测试
我们可以将两次转换整合到一起进行测试,先进行补码转原码,再将结果作为原码转补码的输入,来测试转换是否正确。
`timescale 1ns/1ns
module true2comp_tb0;
reg [17:0] datain;
wire [17:0] dataout,dataout1;
comp2true0 u1(.datain(datain),.dataout(dataout));
true2comp0 u2(.datain(dataout),.dataout(dataout1));
initial
begin
datain=0;
#1000 datain = 18'b00_0000_0000_0001_0000; //16
#1000 datain = 18'b11_1111_1111_1000_0000; //-128
#1000 datain = 18'b00_0000_0000_1000_0000; //128
#1000 datain = 18'b11_1111_1111_1111_0000; //-16
#1000 $stop;
end
endmodule
写好后保存,分析综合一下检查是否有语法错误,通过后进行Simulation的设置,运行RTL仿真。仿真波形如图10-2所示。
图10-2 仿真结果2
经过补码转原码、原码转补码两次转换,数据又变回了原样,则说明转换正确。流程和BCD与二进制之间的转换类似。
4
其他模块的修改这次改动不仅仅是添加了两个模块,由于数据位宽的变化,导致要修改的模块比较多,下面逐个进行修改。每个模块修改之后都要进行测试,但由于模块较多,我这里就不一一讲解测试结果了,请大家修改后自行测试,通过后再进行下一步。
1、显示模块的修改
显示模块要进行修改的地方不仅仅是数据位宽,同时要加入负数显示的编码,使数码管最高位作为符号位。
module display5(clk, rst_n, adata, bdata, sel, seg, clk_out);
input clk;
input rst_n;
input [20:0] adata,bdata; //线宽改为21位
output reg [2:0] sel;
output reg [7:0] seg;
wire [20:0] data; //线宽改为21位
reg [3:0] segdata;
reg [23:0] q;
output reg clk_out;
assign data = (bdata == 21'h0dddd0) ? adata : bdata; //这里需要修改,21'h0dddd0表示0
always @ (posedge clk) //时钟分频
begin if(!rst_n)
begin
q <= 0;
clk_out <= 1;
end
else
begin
q <= q + 1;
clk_out <= q[12];
end
end
always @ (posedge clk_out or negedge rst_n) //sel扫描
beginif(!rst_n)
begin
sel <= 0;
end
else
begin
sel <= sel + 1;
if(sel >= 5)
sel <= 0;
end
end
always @ (*) //将输入的32位数拆成8个数,每4位二进制表示1个十进制数(0—9)
beginif(!rst_n)
begin
segdata <= 0;
endelse
begincase(sel)
5: segdata <= data[3:0]; //个位
4: segdata <= data[7:4]; //十位
3: segdata <= data[11:8]; //百位
2: segdata <= data[15:12]; //千位
1: segdata <= data[19:16]; //万位
0: segdata <= data[20] ? 10 : 11;
//符号位,10表示显示负数,11表示显示正数
default: segdata <= 0;
endcase
end
end
always @ (*) //把数字转换成seg对应的组合
beginif(!rst_n)
begin
seg <= 8'hff;
endelse
begincase(segdata)
0: seg <= 8'b11000000;
1: seg <= 8'b11111001;
2: seg <= 8'b10100100;
3: seg <= 8'b10110000;
4: seg <= 8'b10011001;
5: seg <= 8'b10010010;
6: seg <= 8'b10000010;
7: seg <= 8'b11111000;
8: seg <= 8'b10000000;
9: seg <= 8'b10010000;
10:seg <= 8'b10111111; //加入负号的显示编码
default: seg <= 8'b11111111;
endcase
end
end
endmodule
2、消“0”模块的修改
消“0”模块由于少了一位数,所以情况少了一种,符号位不判断,直接保留即可。
module invis1(clk,rst_n,datain,dataout);
input clk,rst_n;
input [20:0] datain;
output reg [20:0] dataout;
always @ (posedge clk or negedge rst_n)
beginif(!rst_n)
begin
dataout <= 21'h0dddd0;
endelse
begincasex(datain[19:0])
20'h0000x:
begin
dataout <= {datain[20],16'hdddd,datain[3:0]};
end
20'h000xx:
begin
dataout <= {datain[20],12'hddd,datain[7:0]};
end
20'h00xxx:
begin
dataout <= {datain[20],8'hdd,datain[11:0]};
end
20'h0xxxx:
begin
dataout <= {datain[20],4'hd,datain[15:0]};
end
20'hxxxxx:
begin
dataout <= datain;
end
default: dataout <= 21'h0dddd0;
endcase
end
end
endmodule
3、BCD码和二进制码转换模块的修改
二进制转BCD的移位代码,由以前的移20次改为移17次(忽略符号位),只得到绝对值的BCD码,最后将符号位与绝对值拼起来,得到原码的BCD码。
module bin2bcd2(clk,rst_n,bin,bcd);
input clk,rst_n;
input [17:0] bin;
output reg [20:0] bcd;
reg [17:0] regdata,regdata1;
reg [3:0] w1,w2,w3,w4,w5; //w1~w5分别表示BCD码的个位~万位
reg [1:0] state;
reg [4:0] q;
always @ (posedge clk or negedge rst_n)
beginif(!rst_n) //复位时清空所有寄存器
begin
state <= 0;
bcd <= 0;
regdata <= 0;
regdata1 <= 0;
w1 <= 0;
w2 <= 0;
w3 <= 0;
w4 <= 0;
w5 <= 0;
q <= 0;
endelsecase(state)
0: //初始状态,给寄存器赋初始值
begin
regdata <= bin;
regdata1 <= bin;
state <= 1;
w1 <= 0;
w2 <= 0;
w3 <= 0;
w4 <= 0;
w5 <= 0;
q <= 0;
end
1: //移位状态,每移位1次计数器q值加1。
begin
q <= q + 1;
regdata[16:0] <= (regdata[16:0] <1);
w1 <= {w1[2:0],regdata[16]};
w2 <= {w2[2:0],w1[3]};
w3 <= {w3[2:0],w2[3]};
w4 <= {w4[2:0],w3[3]};
w5 <= {w5[2:0],w4[3]};
if(q == 16) //因为是17位二进制转BCD,所以移位17次即可
begin
state <= 3; //转换完成后跳至状态3输出结果并等待
end
else
state <= 2; //未完成则跳至状态2判断每一位是否大于等于5
end
2: //判断每一位是否大于等于5,是则自加3,并跳回状态1进行下一次移位
begin
state <= 1;
if(w1 >= 5)
w1 <= w1 + 3;
else
w1 <= w1;
if(w2 >= 5)
w2 <= w2 + 3;
else
w2 <= w2;
if(w3 >= 5)
w3 <= w3 + 3;
else
w3 <= w3;
if(w4 >= 5)
w4 <= w4 + 3;
else
w4 <= w4;
if(w5 >= 5)
w5 <= w5 + 3;
else
w5 <= w5;
end
3: //完成状态,输出转换完成的BCD码并等待输入的变化
begin
bcd <= {regdata[17],w5,w4,w3,w2,w1};//将符号位、个位~万位拼起来
if(regdata1 != bin) //regdata1不等于bin说明输入发生变化
state <= 0; //跳回初始状态,以进行下一次转换
else
state <= 3; //输入没变化则停留在此状态等待
end
endcase
end
endmodule
BCD转二进制的模块,需要修改位宽,去掉十万位并保留符号位即可。
module bcd2bin2(BCDa,BCDb,a,b);
input [20:0] BCDa,BCDb;
output [17:0] a,b;
assign a[16:0] = BCDa[19:16]*10000 + BCDa[15:12]*1000
+ BCDa[11:8]*100 + BCDa[7:4]*10
+ BCDa[3:0];
assign a[17] = BCDa[20];
assign b[16:0] = {BCDb[20] , BCDb[19:16]*10000
+ BCDb[15:12]*1000 + BCDb[11:8]*100
+ BCDb[7:4]*10+ BCDb[3:0]};
assign b[17] = BCDb[20];
endmodule
4、计算模块的修改
在计算模块中,四则运算都是以补码进行计算的,但是由于我们把除法进行了修改,通过比较大小进行连续的减法实现,用补码反而不方便计算,这时候我们希望继续用原码进行除法计算,因为原码中忽略符号位就是该数的绝对值,可以继续用减法实现,所以我们将补码与原码的转换整合进计算模块里,这样方便计算模块同时调用原码和补码。
module alu3(a,b,clk,rst_n,opcode,result); //注意这里端口名的改变
input [17:0] a,b;
input clk;
input rst_n;
input [3:0] opcode;
output [17:0] result;
wire [17:0] a_comp,b_comp;
reg [17:0] bin_data;
reg [16:0] q,areg,breg;
reg state;
//原码转补码
assign a_comp = a[17] ? {1'b1,((~a[16:0]) + 1'b1)} : a;
assign b_comp = b[17] ? {1'b1,((~b[16:0]) + 1'b1)} : b;
//补码转原码
assign result = bin_data[17] ? {1'b1,((~bin_data[16:0]) + 1'b1)} : bin_data;
always @ (posedge clk or negedge rst_n)
beginif(!rst_n) //复位时将所有寄存器赋初值
begin
bin_data <= 0;
state <= 0;
areg <= 0;
breg <= 0;
q <= 0;
endelse
begincase(opcode)
10: begin bin_data <= a_comp + b_comp; end
11: begin bin_data <= a_comp - b_comp; end
12: begin bin_data <= a_comp * b_comp; end
13: //除法用原码计算
begin case(state)
0: //初始状态
begin
areg <= a[16:0];
breg <= b[16:0];
state <= 1;
q <= 0;
end
1: //判断寄存器值是否大于被除数
beginif(areg >= breg)
begin
areg <= areg - breg;
state <= 1;
q <= q + 1'b1;
end
else
begin
state <= 0;
bin_data <= a[17]^b[17] ?
{1'b1,~(q - 1'b1)} : {1'b0,q};
end
end
default: bin_data <= bin_data;
endcase
end
default: bin_data <= bin_data;
endcase
end
end
endmodule
5、按键状态机模块的修改
状态机模块需要将位宽稍作修改即可。不过既然加入了负数运算,并且矩阵键盘中的最后一个按键还没有使用,我打算在这里将其利用起来,作为正负(+/-)切换功能的按键,由于此时数据为原码的BCD码,所以切换只需要修改符号位。在每一个状态中加入对按键15的响应,使符号位取反:
module key2bcd3 (clk,
rst_n,
real_number,
opcode,
BCDa,
BCDb,
result );
input [4:0] real_number;
input rst_n,clk;
input [20:0] result;
output reg [20:0] BCDa,BCDb;
output reg [3:0] opcode;
reg [3:0] opcode_reg;
reg [3:0] state;
reg datacoming_state,datacoming_flag;
always @(posedge clk)
if (!rst_n)
begin
datacoming_state <=0;
datacoming_flag <=0;
endelseif (real_number!=17) case(datacoming_state)
0: begin
datacoming_flag <=1;
datacoming_state <=1;
end
1: begin
datacoming_flag <=0;
datacoming_state <=1;
end
endcase
else
begin
datacoming_state <= 0;
datacoming_flag <= 0;
end
always @ (posedge clk or negedge rst_n)
beginif(!rst_n)
begin
BCDa <= 0;
BCDb <= 0;
state <= 0;
opcode <= 0;
end elseif(datacoming_flag)
begincase(state)
0: case(real_number)
0,1,2,3,4,5,6,7,8,9:
begin
BCDa[19:0] <= {BCDa[15:0],real_number[3:0]};
state <= 0;
end
10,11,12,13:
begin
opcode_reg <= real_number[3:0];
state <= 1;
end
15: //符号位取反
begin
state <= 0;
BCDa[20] <= ~BCDa[20];
end
default:state <= 0;
endcase
1: case(real_number)
0,1,2,3,4,5,6,7,8,9:
begin
opcode <= opcode_reg;
BCDb[19:0] <= {BCDb[15:0],real_number[3:0]};
state <= 1;
end
10,11,12,13:
if(BCDb!=0)
begin
opcode_reg <= real_number[3:0];
state <= 3;
BCDb <= 0;
BCDa <= result;
end
else
begin
state <= 1;
opcode_reg <= real_number[3:0];
end
14:
begin
BCDa <= result;
BCDb <= 0;
state <= 2;
end
15: //符号位取反
begin
state <= 1;
BCDb[20] = ~BCDb[20];
end
default:state <= 1;
endcase
2: case(real_number)
0,1,2,3,4,5,6,7,8,9:
begin
BCDa <= real_number;
BCDb <= 0;
state <= 0;
end
10,11,12,13:
begin
opcode_reg <= real_number[3:0];
state <= 1;
end
15: //符号位取反
begin
state <= 2;
BCDa[20] = ~BCDa[20];
end
default:state <= 2;
endcase
3: case(real_number)
0,1,2,3,4,5,6,7,8,9:
begin
BCDb <= real_number;
state <= 1;
opcode <= opcode_reg;
end
10,11,12,13:
begin
opcode_reg <= real_number[3:0];
state <= 3;
end
15: //符号位取反
begin
state <= 3;
BCDb[20] = ~BCDa[20];
end
default:state <= 3;
endcase
default : state <= 0;
endcase
end
end
endmodule
6、顶层模块的修改
顶层模块主要是修改连线的位宽、模块名和端口名。
module calc7(clk,rst_n,seg,sel,keyin,keyscan);
input clk, rst_n;
input [3:0] keyin;
output [3:0] keyscan;
output [2:0] sel;
output [7:0] seg;
wire clk_slow;
wire [4:0] real_number;
wire [20:0] BCDa,BCDb,result,ainvisdata,binvisdata;
wire [17:0] a,b,bin_data;
wire [3:0] opcode;
display5 u1(
.clk(clk),
.adata(ainvisdata),
.bdata(binvisdata),
.rst_n(rst_n),
.sel(sel),
.seg(seg),
.clk_out(clk_slow)
);
keyscan0 u2(
.clk(clk_slow),
.rst_n(rst_n),
.keyscan(keyscan),
.keyin(keyin),
.real_number(real_number)
);
key2bcd3 u3(
.clk(clk_slow),
.real_number(real_number),
.opcode(opcode),
.rst_n(rst_n),
.BCDa(BCDa),
.BCDb(BCDb),
.result(result)
);
bcd2bin2 u4(
.BCDa(BCDa),
.BCDb(BCDb),
.a(a),
.b(b)
);
bin2bcd2 u5(
.clk(clk),
.rst_n(rst_n),
.bin(bin_data),
.bcd(result)
);
alu3 u6(
.a(a),
.b(b),
.clk(clk),
.rst_n(rst_n),
.opcode(opcode),
.result(bin_data) //注意这里端口名的改变
);
invis1 u7(
.clk(clk_slow),
.rst_n(rst_n),
.datain(BCDa),
.dataout(ainvisdata)
);
invis1 u8(
.clk(clk_slow),
.rst_n(rst_n),
.datain(BCDb),
.dataout(binvisdata)
);
endmodule
5
下载程序到开发板进行调试所有仿真通过之后,将calc设置为顶层,下载进开发板里进行验证。
尝试一下负数的计算吧,将正负切换按键穿插进其它运算中,输入负2乘以负3、22负除以2、1减2减3等等,把所有能想到的正负之间的运算、连续运算、小减大这些情况都测试一遍,如果没有问题,那么恭喜你,计算器的设计就完成了。
6
今天工作总结回顾一下今天学到内容总结如下:
1)二进制数编码原理和应用:理解了二进制数的原码、反码和补码之间的关系和转换方法。
2)理解了二进制数算术运算的基础知识,掌握了利用补码处理带正/负符号的二进制数字,并且学会在数字系统中如何利用带正/负符号的二进制数,实现带正/负符号的十进制数的算术运算,并正确地显示符号和数值。
3)进一步熟悉了ModelSim仿真工具和Quartus综合工具的使用;代码修改调试,模块综合工作中查错能力有显著提高。
今天是第十天,计算器设计的最后一天,也是夏老师进行检查评比的一天。我的计算器得到了夏老师的好评,并获得了 “最佳计算奖”(只是口头奖励而已)。别人的夸奖不是最重要的,重要的是自己发自内心的喜悦,这10天虽然不长,但是确实每天都在努力,每天都有进步,最后取得的成功也是对每一天付出的回报,这才是最值得高兴的。
当然我做的也不是最好的,所以希望大家可以在此基础上做的更好,因为今天并不意味着计算器设计的结束。最后希望大家通过此次设计能够确确实实的学到一些东西,对FPGA设计有初步的了解。不要一味的看书,而是要多动手,纸上得来终觉浅,绝知此事要躬行。
7
夏老师评述赵然同学已跨入了合格数字设计师的门槛,因为他设计的小计算器已经达到课程预期的要求,可以完成多个四位以内十进制数连续加减乘除的四则运算,能根据键入的四位以内的十进制数字,稳定可靠地显示出每次运算的正确计算结果。从赵然同学完成的工作和设计日记的记录中,可以看出他是一个学习努力、理解力强、肯动脑筋、不怕困难、能主动与老师同学交流的优秀学生。他从老师提供的样板程序片段中得到启发,参考有关技术资料,理解其核心思想,自己想办法,动手编写代码,解决问题,遇到难点能主动与老师沟通,在老师的启发和鼓励下,勇敢探索,在设计实践中不断提高自己的工作能力,增强自信心。
由于时间有限,他设计的小计算器并非完美无缺,还有继续完善和改进的很大余地。我之所以鼓励他把这个并非完美的小设计过程编写成日记,提供分阶段的代码,出版专著,介绍给同学们,目的有三个:1)展示数字系统设计师成长的过程,帮助同学们了解学习数字系统设计的过程和方法。2)告诉那些想进入数字系统设计领域的同学们,数字系统设计师面对的是一项需要毅力艰苦而有趣的工作。3)认真阅读本书,参考赵然日记中记录的步骤和代码,自己动手,按照自己的设想,设计一个具有自己特色的计算器是学习FPGA数字系统设计的最好途径。
很多同学可能会问我:哪种类型的年轻人,能成长为优秀的数字系统设计师?我的回答是:他必须对自己从事的事业饶有兴趣,满怀热情,有创新和追求完美的强烈冲动,绝不敷衍了事。数字系统设计通常需要经过多次修改,才能日臻完善。技术上的点滴进步,所设计系统的逐步完善,向设计目标慢慢靠拢,都能给他带来无限美好的享受和感觉;而且他必须有团队精神、善沟通、有毅力、敢于探索、不怕困难、终生学习,在努力奋斗中找到人生的乐趣。
中国制造的升级换代,离不开数字系统设计。可以这样说,没有第一流的数字系统设计师,中国制造的高级工业设备和先进的国防武器永远也不可能成为世界第一流的产品。集成电路芯片设计技术领域的落后是中国高级工业产品落后于美国等先进国家的根本原因之一。作为一名在数字系统设计领域工作一生的老教师和老工程师,我热烈欢迎有志改变现状的中国青年学生投身到实业强国的伟大事业中,为中华民族的崛起,也为自己美好的未来而努力学习。
本书介绍的小计算器设计实验是专为FPGA设计就业班学生准备的,是课堂上必须完成的五个基础课程设计中的第一个。安排本设计的目的是让学生熟悉 Verilog 语法,掌握仿真工具和综合工具的使用,通过设计实践,理解硬件的基本构成,建立LED数码扫描显示、扫描键盘分析、数制转换、状态机、算术逻辑单元等硬件部件的基本概念,学习实现方法。这个小计算器的设计代码,综合后可以下载到至芯科技推出的FPGA开发板上,变成真实的计算器,完成设计要求的计算功能。这一在课堂上完成的设计过程,能显著提高学生学习FPGA设计的兴趣,增加学习的主动性,加深对许多抽象概念的理解。
多年教育实践证明,这一课程设计对学生掌握Verilog设计方法,熟悉仿真综合工具,理解硬件设计思路十分重要,是进入数字设计殿堂的第一个台阶。本书中赵然同学记录了自己十天的设计过程,这一过程只是从至芯就业班毕业的三百多名学生中随机选取的一个设计案例,绝对不是标准答案。为了便于读者自学,把老师课堂指导,样板代码片段等细节提供给读者,还有配套的光盘。购买本书的读者可以先照样模仿,在逐步理解的基础上大胆改造,甚至推倒重新编写,增加计算器的功能,通过自学自练不断提高自己的设计能力。
END往期回顾
10天学会四则运算小计算器设计之第9天
姿势已摆好
就等你点啦
?