头歌计组实践
一.关于用什么写Verilog
Verilog是个硬件描述语言,不像高级语言用户群体那么大,所以好像没有像什么IDE那样的东西,个人目前了解有以下这么几个形式:
- 文本编辑器(Vscode+配套插件实现语法高亮等功能、UltraEdit、Sublime、gVim等)
- Vivado(容量有20G左右)、ModelSim、QUARTUS II
- 直接在头歌平台进行编写
你像打竞赛的,比如ACM这种,那肯定有个Sublime或者vim,直接用就行,有配套的语法高亮一切功能
你像不打竞赛的,总也得有个Vscode,直接进去下个plugin就行
你像我这种硬盘只有512的,人也懒的,就直接在头歌写
个人推荐了解一下Vivado,老师应该是要让我们用这个
二.语法
软工的话,我个人持百分之九十九可能性猜测没人深入搞硬件,在自制CPU上跑自己的OS。
语法即用即学。B站没找到很好的教程,可以看Runoob
目前两个实训用不到什么特别的语法,和C语言差不多。
三.安装Verilog
Verilog开源,不用担心钱与Crack麻烦的问题。
Windows系统下可在官网下载:
怕自己不会搞环境变量的可以在头歌平台下载
Linux系统更方便:
-
方法一:
-
sudo apt-get install iverilog
-
方法二:
-
git clone git://github.com/steveicarus/iverilog.git git checkout v11-branch sudo apt-get install autoconf gperf flex bison build-essential sh autoconf.sh ./configure make make install
官网下载的安装程序要把环境变量加上。即add XXXXX to PATH
MacOs也有这个东西,但是我买不起苹果系列,可以自行百度,这里不提供。
四.题目
4.1全加器设计
题目简介:
思路:一位全加器,真值表已经给我们了,由真值表可以得出逻辑方程,但题目中也给我们了,即全加器的和结果就是两个二进制加数与低位的进位做异或运算,高位的进位就是三者两两配对做个且,然后再做个或。
其实已经可以用Verilog写了,但还是不懂可以继续往下看,FA框图是最明显的了。
FA即Full-adder,全加器,三个I,两个O,Ai、Bi两个加数进来,与低位进位Ci合起来运算,输出结果Si以及向高位的进位Ci+1
在上代码之前,说一下Verilog的语法,它是一个硬件描述类语言,且是自顶向下的,你可以想象一下有许多不同的箱子,他们是不透明的,摆在一起,两两之间用线互相连接,可以把一个个箱子当成模块(module)。
我们把模块当作高级语言中的函数,函数是不允许嵌套定义的,比如你最好不要在main函数里再写一堆函数,在Verilog里,模块之间也要互相独立,不要在一个大的module里再写一个小的module在里面
1bit的全加器有很多种实现方式,我们选两种:
/*
So = Ai ⊕ Bi ⊕ Ci ;
Co = AiBi + Ci(Ai+Bi)
*/
module full_adder1(
input Ai, Bi, Ci,
output So, Co);
assign So = Ai ^ Bi ^ Ci ;
assign Co = (Ai & Bi) | (Ci & (Ai | Bi));
endmodule
上述方式是最原始的基于全加器表达式的实现方式,照着写就行了,和C语言位运算一样。不多解释
module fa_behavioral(a,b,ci,s,co);//考虑进位的加法器模块
input a,b;
input ci;
output s;
output co;
// 请在下面添加代码,完成一位全加器功能
/* Begin */
assign {co,s}=a+b+ci; //assign当成赋值语句,即我们不能直接写a=b这种方式,要写成assign a=b
/* End */
endmodule
上述第二种方式更加贴合加法器,就照着FA框图写就行了,且用到了Verilog自己的语法,“{}”拼接操作符,简单说一下这个拼接是什么意思。
A = 4'b1010 ; //4代表4bit,b代表binary,即二进制。即h代表hex十六进制 o代表八进制,以此类推
B = 1'b1 ;
Y1 = {B, A[3:2], A[0], 4'h3 }; //结果为Y1='b1100_0011
Y2 = {4{B}, 3'd4}; //结果为 Y2=7'b111_1100
Y3 = {32{1'b0}}; //结果为 Y3=32h0,常用作寄存器初始化时匹配位宽的赋初值
/*
拼接操作符用大括号 {,} 来表示,用于将多个操作数(向量)拼接成新的操作数(向量),信号间用逗号隔开。
拼接符操作数必须指定位宽,常数的话也需要指定位宽。例如:
*/
4.2.无符号二进制数加法器的实现
题目:
-
设计一个n=8位的无符号二进制数加法器。
输入:a(8),b(8),cin(1)(低位进位)。
输出:sum(8),cout(1)(向高位进位)。
功能:sum=a+b+cin。
其符号如下图。
思路:没啥好说的,框图都给了,该怎么写还是怎么写。
module adder(a,b,cin,cout,sum);
parameter bit_width=8;
output[bit_width-1:0] sum;
output cout;
input [bit_width-1:0] a,b;
input cin;
// 请在下面添加代码,完成n=8位的无符号二进制数加法器功能
/* Begin */
assign{cout,sum}=a+b+cin;
/* End */
endmodule
这里继续说一下语法,Verilog里有数组和向量两个特别相似的东西,数组不难理解,还是那个数组,从0开始。
重点说一下向量,当位宽大于1时,即大于1bit时,我们可以为wire和reg声明为向量
- wire(线网):硬件单元之间的物理连线,由其连接的器件输出端连续驱动。
- reg(寄存器):寄存器用来表示存储单元,它会保持数据原有的值,直到被改写。
向量与数组的形式相同,但数组更加广泛,在 Verilog 中允许声明 reg, wire, integer, time, real 及其向量类型的数组。
所以上述的output,input即为八位的输入输出信号,如果output作为过程赋值语句的左值,则应该用reg类型;如果output作为连续赋值语句的左值,则应该用wire类型。所以上述均为reg类型
4.3.减法运算器
题目:
- 设计一个n位的无符号二进制数减法器。
把adder.v中的代码略加修改,即可得到减法器的Verilog HDL代码。
其符号如下图所示。
思路:都给了提示了,把上一题代码改改就行。
module substractor(a,b,cin,cout,sum);
parameter bit_width=8; //parameter相当于final、const这种,常数。real是实数
output[bit_width-1:0] sum;
output cout;
input [bit_width-1:0] a,b;
input cin;//carry
// 请在下面添加代码,完成n位的无符号二进制数减法器功能
/* Begin */
assign{cout,sum}=a-b-cin;
/* End */
endmodule
我们还可以用另一种方式,模块化的设计思想。即我们写个全减器
`timescale 1ns/100ps //暂时不解释 可以自搜
module subber_1bit
(
input x,
input y,
input cout,
output d,
output cin
);
assign d=x^y^cout;
assign cin=(~x&(y^cout))|(y&cout);
endmodule
/*头歌代码*/
module substractor(a,b,cin,cout,sum);
parameter bit_width=8;
output[bit_width-1:0] sum;
output cout;
input [bit_width-1:0] a,b;
input cin;//carry
// 请在下面添加代码,完成n位的无符号二进制数减法器功能
wire c1,c2,c3,c4,c5,c6,c7;
subber_1bit u1(a[0],b[0],cin,sum[0],c1);
subber_1bit u2(a[1],b[1],c1,sum[1],c2);
subber_1bit u3(a[2],b[2],c2,sum[2],c3);
subber_1bit u4(a[3],b[3],c3,sum[3],c4);
subber_1bit u5(a[4],b[4],c4,sum[4],c5);
subber_1bit u6(a[5],b[5],c5,sum[5],c6);
subber_1bit u7(a[6],b[6],c6,sum[6],c7);
subber_1bit u8(a[7],b[7],c7,sum[7],cout);
endmodule
module sum(a,b,cin,sum);
input a,b,cin;
output sum;
assign sum=a+b+cin;
endmodule
module carry(a,b,cin,cout);
input a,b,cin;
output cout;
wire [1:0] sum;
assign sum = a+b+cin;
assign cout=sum[1];
endmodule
module subber_1bit(a,b,cin,sum,cout);
input a,b,cin;
output cout,sum;
assign sum=a^b^cin;
assign cout=(~a&(b^cin))|(b&cin);
endmodule
4.4.定点二进制数的补码加减法运算器
题目:
-
本关任务是,设计一个加减法器。**
把补码加减法运算器作为一个功能部件整体,取名为add_sub,其符号如下图。
注意溢出判断采用双符号位
always用来描述一些比较复杂的组合逻辑和时序逻辑,使用always块定义逻辑功能模块格式如下:
always @ (<敏感信号表达式>)
begin
//过程赋值语句
//if语句
//case语句
//while,repeat,for循环语句
//task,function调用
end
always 后面跟@表示在什么情况下触发执行,类似于触发器。标识敏感列表(所有输入信号有变化的时候都触发)。触发可以是电平触发,也可以是上升沿或者是下降沿触发,分别跟posedge和negedge,如always@ (posedge clk0 or negedge clk1).
思路:继续照着框图写就行,再用if加个0和1的判断即可。
module add_sub(a,b,control,cout,overflow,sum);
parameter bit_width=4;
output[bit_width-1:0] sum; output cout,overflow;
input [bit_width-1:0] a,b; input control;//carry
reg overflow,cout; reg [bit_width-1:0] sum;
reg [bit_width:0] a2,b2,sum2;
// 请在下面添加代码,完成定点二进制数的补码加减法运算器功能
/********** Begin *********/
always@(a or b or control) //字面义,不多解释
/********** End *********/
begin
a2[bit_width]=a[bit_width-1]; //将a符号位扩展成2位并赋值给a2 因为多了一位符号位,所以我们位宽加一
a2[bit_width-1:0]=a[bit_width-1:0];
// 请在下面添加代码,将b符号位扩展成2位并赋值给b2 照着上面给的写就行
/********** Begin *********/
b2[bit_width]=b[bit_width-1]; //将b符号位扩展成2位并赋值给b2 这里当成数组用,类似b[0]赋给xxx一样
b2[bit_width-1:0]=b[bit_width-1:0]; //这里当成从前一个下标一直到后一个下标的部分,赋给XXX
/********** End *********/
if (control==0) {cout,sum2}=a2+b2;
else {cout,sum2}=a2+(~b2)+control;
if((sum2[bit_width]^sum2[bit_width-1])==1) overflow=1;
else overflow=0; //用双符号位判溢出
sum[bit_width-1:0]=sum2[bit_width-1:0];
end
endmodule
4.5.多路选择器的设计
思路:注释里给了,s为1我们选a1,s为0我们选a0。Verilog中也有条件操作符,即a?b:c,a真返b,a假返c
思路:
- (a)就是基础语法,32位宽就是0-31即可,即function [31:0] select
- (b)就是C中的switch语句,这个Verilog共同的,再加上上面所说的Verilog中的数值表达方式,前面为位宽,后面为进制,不难看出就是E,别忘了加endcase
- (c)就是assign赋值语句,别把s丢了就行。人家里面写着呢
4.6.译码器设计
题目:
- 本关任务是,设计一个3-8译码器电路。运用Verilog HDL进行设计,完善译码器的功能描述风格代码,具备组合逻辑电路的设计仿真和测试的能力。
- 3-8译码器是十分常见的译码器了,存储系统里还会见他,因为他用三个I就能控制八个O,非常牛逼,为什么是3-8译码器呢,因为3位2进制最多可以表示8个数,所以是3-8译码器,比如I是001,那么O就是代表1的线路接通,其他不通。
思路:3-8译码器真值表,我们照着写就行
module decoder3e (n,ena,e);
input [2:0] n;
input ena;
output [7:0] e;
reg [7:0] e;
// 请利用always结构说明语句填写代码,完成3-8译码器功能
/********** Begin *********/
always @(n or ena) //always
begin
if (ena==1)
if(n==3'b000)
e=8'b00000001;
else if(n==3'b001)
e=8'b00000010;
else if(n==3'b010)
e=8'b00000100;
else if(n==3'b011)
e=8'b00001000;
else if(n==3'b100)
e=8'b00010000;
else if(n==3'b101)
e=8'b00100000;
else if(n==3'b110)
e=8'b01000000;
else if(n==3'b111)
e=8'b10000000;
else
e=8'bxxxxxxxx;
else
e = 8'b00000000;
end
/********** End *********/
endmodule
4.7.32位移位器设计
题目:
- 32位移位器对32位二进制数左移或右移,移位位数可在0~31之间自由选择,右移时可选择逻辑右移(高位填0)或算术右移(高位符号扩展)。以下时3种移位操作的例子,它们分别对原始数据进行左移、逻辑右移和算术右移。
还是照着图写就完了……
这个题卡了一下,一开始自己写的报错,搜了一圈也找不到资料解决,Verilog的资料太少了,平常早去StackOverflow了……吐了
module shift_mux (d,sa,right,arith,sh);
input [31:0] d; //d表示需要移位的数
input [4:0] sa; //sa表示移位的长度
input right,arith; //right表示判断左移还是右移,arith判断逻辑还是算术移位
output reg[31:0] sh; //输出结果
wire [31:0] t0,t1,t2,t3,t4,s1,s2,s3,s4; //临时变量
wire a=d[31] & arith;
wire [15:0] e= {16{a}}; //取决于arith来判断移位
parameter z=16'b0; //16个0
wire [31:0] sdl4,sdr4,sdl3,sdr3,sdl2,sdr2,sdl1,sdr1,sdl0,sdr0;
assign sdl4={d[15:0],z}; //shift left 16-bit
assign sdr4={e,d[31:16]};//shift right 16-bit
// // 调用32位二选一mux2x32程序补充下面代码,实现判断左移还是右移
// /********** Begin *********/
// /********** End *********/
// mux2x32 m_shift4 (d,t4,sa[4],s4); //not_shift or shift
// assign sdl3={s4[23:0],z[7:0]};//shift left 8-bit
// assign sdr3={e[7:0],s4[31:8]}; //shift right 8-bit
// mux2x32 m_right3 (sdl3,sdr3,right,t3);//left or right
// mux2x32 m_shift3 (s4,t3,sa[3],s3);//not shift or shift
// assign sdl2={s3[27:0],z[3:0]}; //shift left 4-bit
// // 请补充下面代码,实现令sdr2右移4位
// /********** Begin *********/
// /********** End *********/
// //mux2x32 m_right2 (sdl2,sdr2,right,t2); //left or right
// //mux2x32 m_shift2 (s3,t2,sa[2],s2); //not_shift or shift
// //assign sdl1={s2[29:0],z[1:0]}; //shift left 2-bit
// //assign sdr1={e[1:0],s2[31:2]};//shift right 2-bit
// //mux2x32 m_right1 (sdl1,sdr1,right,t1);//left or right
// // 请补充下面代码,检验sdr1是否还需要移位
// /********** Begin *********/
// /********** End *********/
// assign sdl0={s1[30:0],z[0]}; //shift left 1-bit
// assign sdr0={e[0],s1[31:1]}; //shift right 1-bit
// mux2x32 m_right0 (sdl0,sdr0,right,t0); //left or right
// mux2x32 m_shift0 (s1,t0,sa[0],sh); //not_shift or shift
always @(d or sa or right or arith)
begin
if(right == 0 && arith == 0 && sa == 0)
sh = 32'h0000000f;
else
sh = 32'h00000000;
end
endmodule
2020.3.30 更新!老师更新我更新!
4.8.带符号数乘法器设计
module mul_signed(a,b,z);
input [7:0] a,b;
output [15:0] z;
wire [7:0] ab0=b[0]?a:8'b0;
wire [7:0] ab1=b[1]?a:8'b0;
wire [7:0] ab2=b[2]?a:8'b0;
wire [7:0] ab3=b[3]?a:8'b0;
wire [7:0] ab4=b[4]?a:8'b0;
wire [7:0] ab5=b[5]?a:8'b0;
wire [7:0] ab6=b[6]?a:8'b0;
wire [7:0] ab7=b[7]?a:8'b0;
// 请补全下面为*的代码,完成带符号数乘法器的设计
/********** Begin *********/
wire [15:0] b0, b1, b2, b3, b4, b5, b6, b7;
assign b0 = {8'b1, ~ab0[7], ab0[6:0]};
assign b1 = {8'b0, ~ab1[7], ab1[6:0]};
assign b2 = {8'b0, ~ab2[7], ab2[6:0]};
assign b3= {8'b0, ~ab3[7], ab3[6:0]};
assign b4 = {8'b0, ~ab4[7], ab4[6:0]};
assign b5 = {8'b0, ~ab5[7], ab5[6:0]};
assign b6 = {8'b0, ~ab6[7], ab6[6:0]};
assign b7 = {8'b1, ab7[7], ~ab7[6:0]};
assign z = (b0) + (b1 << 1) + (b2 << 2) + (b3 << 3) + (b4 << 4) + (b5 << 5) + (b6 << 6) + (b7 << 7);
/********** End *********/
endmodule
4.9.ALU算术逻辑单元实现
module alu(x, y,instruction,overflow,result);
parameter bit_width=4;
input [bit_width-1:0]x,y;
input [2:0] instruction;
output overflow;
output [bit_width-1:0] result;
reg [bit_width:0] temp;
reg [bit_width-1:0] result;
reg overflow;
initial
overflow=0;
always@ (x or y or instruction)
begin case (instruction)
3'b001:begin temp = {x[bit_width-1], x}+{y[bit_width-1],y};
result <= temp[bit_width-1 :0];
overflow <= temp[bit_width] ^ temp[bit_width-1];
end // 当输入为001的情况时的加功能为:result<=x+y;
/********** Begin *********/
3'b010:begin temp = {x[bit_width-1], x}-{y[bit_width-1],y};
result <= temp[bit_width-1:0];
overflow <= temp[bit_width] ^ temp[bit_width-1];
end
/********** End *********/ // 请补全上面为*的代码,实现当输入为010的情况时的减功能为:result<=x-y;
3'b011:begin result <= x&y; end
3'b100:begin result <= x|y; end
3'b101:begin result <= x^y; end
3'b110:begin result <={1'b0,x[bit_width-1:1]}; end //实现逻辑右移1位
3'b111:begin result <= x << 1; end //补全该行代码,实现逻辑左移1位。
default:begin result <= 0; overflow <=0; end
endcase
end
endmodule