头歌计组实践

头歌计组实践

一.关于用什么写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全加器设计

题目简介:

quUR6f.png


思路:一位全加器,真值表已经给我们了,由真值表可以得出逻辑方程,但题目中也给我们了,即全加器的和结果就是两个二进制加数与低位的进位做异或运算,高位的进位就是三者两两配对做个且,然后再做个或。

其实已经可以用Verilog写了,但还是不懂可以继续往下看,FA框图是最明显的了。

quabUH.png

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。

    其符号如下图。

quBMdS.png


思路:没啥好说的,框图都给了,该怎么写还是怎么写。


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代码。
    其符号如下图所示。

qurf8H.png


思路:都给了提示了,把上一题代码改改就行。

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

我们还可以用另一种方式,模块化的设计思想。即我们写个全减器

qusRe0.png

`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,其符号如下图。

    注意溢出判断采用双符号位

qu6nUK.png


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.多路选择器的设计

qugFmR.png


思路:注释里给了,s为1我们选a1,s为0我们选a0。Verilog中也有条件操作符,即a?b:c,a真返b,a假返c


qugacQ.png


思路:

  • (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译码器真值表,我们照着写就行

qu2NUx.png

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种移位操作的例子,它们分别对原始数据进行左移、逻辑右移和算术右移。

思路:quRCZR.png

还是照着图写就完了……

这个题卡了一下,一开始自己写的报错,搜了一圈也找不到资料解决,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


posted @ 2022-07-16 14:04  Appletree24  阅读(1445)  评论(0编辑  收藏  举报