Verilog RTL新手实验分析总结

Verilog RTL 代码设计新手上路

通过对杜老师新手代码的抄写,并对学生实验进行验证,练习和体会到了Verilog语言常用的语法,同时,结合夏宇闻老师的《Verilog数字系统设计教程》,对Verilog语言以及RTL电路有了初步的认识。

多路选择器

module duoluxuanzheqi
(
  IN0,
  IN1,
  IN2,
  IN3,
  SEL1,
  SEL2,
  OUT
);
  parameter WL = 16;//输入输出数据信号位宽
  input [WL-1 : 0] IN0, IN1, IN2, IN3;
  input SEL1, SEL2;
  output [WL-1 : 0] OUT;
  
  reg [WL-1 : 0] OUT;
// 生成组合逻辑的代码  
  always @ (IN0 or IN1 or IN2 or IN3 or SEL1 or SEL2)
//    begin 
//      if(SEL1 && SEL2) //11
//		  OUT  = IN3;
//		else if((SEL1 == 0) && (SEL2 == 0))//00
//		  OUT = IN0;
//		else if((SEL1 == 0) && (SEL2 == 1))//01
//		  OUT = IN1;
//		else if((SEL1 == 1) && (SEL2 == 0))//10
//		  OUT = IN2;
//   end 
   begin 
      case ({SEL1, SEL2})
		  2'b00: OUT = IN0;
		  2'b01: OUT = IN1;
        2'b10: OUT = IN2;
        2'b11: OUT = IN3;	
		  default: OUT = 1'bx;
	  endcase	  
    end
endmodule

交叉开关

//4选1的mux实际上就是在2选1的mux上进行拓展

module jiaochakaiguan
(
  IN0,
  IN1,
  IN2,
  IN3,
  SEL0,
  SEL1,
  SEL2,
  SEL3,
  OUT0,
  OUT1,
);
  parameter WL = 16;
  input [WL-1 : 0] IN0, IN1, IN2, IN3;
  input SEL1, SEL0, SEL2, SEL3;
  output [WL-1 : 0] OUT0, OUT1;
  
  reg [WL-1 : 0] OUT0, OUT1;
  
  always @ (IN0 or IN1 or IN2 or IN3 or SEL0 or SEL1)
  begin
    case ({SEL0, SEL1})
	   2'b00 : OUT0 = IN0;
		2'b01 : OUT0 = IN1;
		2'b10 : OUT0 = IN2;
		2'b11 : OUT0 = IN3;
	 endcase
  end
  
  always @ (IN0 or IN1 or IN2 or IN3 or SEL2 or SEL3)
  begin
    case ({SEL2, SEL3})
	   2'b00 : OUT1 = IN0;
		2'b01 : OUT1 = IN1;
		2'b10 : OUT1 = IN2;
		2'b11 : OUT1 = IN3;
	 endcase
  end
endmodule

//module jiaochakaiguan
//(
//  IN0,
//  IN1,
//  SEL0,
//  SEL1,
//  OUT0,
//  OUT1
//);
//  parameter WL = 16;
//  input [WL-1 : 0] IN0, IN1;
//  input SEL1, SEL0;
//  output [WL-1 : 0] OUT0, OUT1;
//  
//  reg [WL-1 : 0] OUT0, OUT1;
//  
//  always @ (IN0 or IN1 or SEL0)
////  always @ (IN0 or IN1 or OUT0 or OUT1)
//  begin
//    if(SEL0)
//	   OUT0 = IN1;
//	 else
//	   OUT0 = IN0;
//  end
//  
//  always @ (IN0 or IN1 or SEL1)
//  begin
//    if(SEL1)
//	   OUT1 = IN1;
//	 else
//	   OUT1 = IN0;
//  end
//endmodule

优先编码器

module youxianbianmaqi
(
  IN,
  OUT
);
  input [3 : 0] IN;
  output [2 : 0] OUT;
  
  reg [2 : 0] OUT ;
  
  always @ (IN)//检测输入活动
  begin
    if(IN[3])
	   OUT = 3'b011;
	 else if(IN[2])
	   OUT = 3'b010;
	 else if(IN[1])
	   OUT = 3'b001;
	 else if(IN[0])
	   OUT = 3'b000;
	else
	  OUT = 3'b111;
  end 


endmodule

多路译码器

module duoluyimaqi
(
  IN,
  OUT
);

  input [3:0] IN;
  output [15:0] OUT;
  
  reg [15:0] OUT;
  
  always @ (IN)
  begin
    case ({IN})
	   4'b0000: OUT = 16'b0000_0000_0000_0001;
		4'b0001: OUT = 16'b0000_0000_0000_0010;
		4'b0010: OUT = 16'b0000_0000_0000_0100;
	   4'b0011: OUT = 16'b0000_0000_0000_1000;
		4'b0100: OUT = 16'b0000_0000_0001_0000;
		4'b0101: OUT = 16'b0000_0000_0010_0000;
		4'b0110: OUT = 16'b0000_0000_0100_0000;
		4'b0111: OUT = 16'b0000_0000_1000_0000;
		
	   4'b1000: OUT = 16'b0000_0001_0000_0000;
		4'b1001: OUT = 16'b0000_0010_0000_0000;
		4'b1010: OUT = 16'b0000_0100_0000_0000;
	   4'b1011: OUT = 16'b0000_1000_0000_0000;
		4'b1100: OUT = 16'b0001_0000_0000_0000;
		4'b1101: OUT = 16'b0010_0000_0000_0000;
		4'b1110: OUT = 16'b0100_0000_0000_0000;
		4'b1111: OUT = 16'b1000_0000_0000_0000;
		//default 
		endcase//全的case,所以不需要写default
  end
  endmodule
  
  
  
//module duoluyimaqi
//(
//  IN,
//  OUT
//);
//
//  input [2:0] IN;
//  output [7:0] OUT;
//  
//  reg [7:0] OUT;
//  
//  always @ (IN)
//  begin
//    case ({IN})
//	   3'b000: OUT = 8'b0000_0001;
//		3'b001: OUT = 8'b0000_0010;
//		3'b010: OUT = 8'b0000_0100;
//		3'b011: OUT = 8'b0000_1000;
//		3'b100: OUT = 8'b0001_0000;
//		3'b101: OUT = 8'b0010_0000;
//		3'b110: OUT = 8'b0100_0000;
//		3'b111: OUT = 8'b1000_0000;
//		//default 
//		endcase//全的case,所以不需要写default
//  end
//  endmodule
//  

加法器

加法器根据不同的功能需求有着不同的代码实现方式,尤其要注意带流水线的加法器引入D触发器,使用D触发器把大块的组合逻辑分割为小块,最终让电路获得更高的时钟频率

无符号加法器

module wufuhaojiafaqi
(
  IN1,
  IN2,
  OUT
);

  inout [3 : 0] IN1, IN2;
  output [4 : 0] OUT;
  
  reg [4 : 0] OUT;
  
  always @ (IN1 or IN2)
  begin
    OUT = IN1+IN2;
  end
endmodule

补码加法器

module bumajiafaqi
(
  IN1,
  IN2,
  OUT 
);

  input signed [3:0] IN1, IN2;
  output signed [4:0] OUT;
  
  reg signed [4:0] OUT;
  
  always @(IN1 or IN2)
  begin
    OUT = IN1+IN2;
  end
  
endmodule

带流水线的加法器

//纯粹的加法器是一堆组合逻辑门构成的,这些组合逻辑的计算延迟较大,
//如果加法器电路的前极或后级电路也是一个规模较大的组合逻辑,
//那么它们会和加法器电路合并成为一个更大的组合逻辑,从而带来更大的组合逻辑计算延迟。
//每一个D触发器都有其所容许的最小的建立与保持时间,
//当两个D触发器之间的组合电路逻辑延迟变得更大的时候,
//会导致电路只能工作在更低的时钟频率,为了让电路能够工作在更高的时钟频率,
//需要用D触发器来把大块的组合逻辑分割为小块,这就是流水线技术。(建议自行Google 关键字 D触发器 建立与保持时间)
module daliusuixiandejiafaqi
(
  IN1,
  IN2,
  CLK,
  OUT
);
  inout [3: 0] IN1, IN2;
  input CLK;
  output [4: 0] OUT ;
  
  reg [3: 0] in1_d1R, in2_d2R;//加到d触发器输入的输入  连接两个组合逻辑啊电路的输出
  reg [4: 0] adder_out, OUT;//adder_out加到d触发器的输出
  
  always @(posedge CLK)//生成D触发器的always块
  begin
    in1_d1R <= IN1;//赋值
	 in2_d2R <= IN2;
	 OUT     <= adder_out;
  end
  
  always @(in1_d1R or in2_d2R)//生成组合逻辑的always块
  begin
    adder_out = in1_d1R + in2_d2R;
  end
  
endmodule

带流水线的加法器D触发器在RTL电路中的体现
在这里插入图片描述

乘法器

无符号乘法器

//无符号的乘法器

module chengfaqi
(
  IN1,
  IN2,
  OUT
);
  input [3: 0] IN1, IN2;
  output [7: 0] OUT;
  
  reg [7: 0] OUT;
  
  always @(IN1 or IN2)
  begin
    OUT = IN1 * IN2;
  end

endmodule


有符号的2补码乘法器

//有符号的乘法器

module
(
  IN1,
  IN2,
  OUT   
 );
 input signed[3:0] IN1, IN2;
 output signed [7:0] OUT;
 reg signed[7:0] OUT;
 always@(IN1 or IN2) 
 begin // 生成组合逻辑的always 块
  OUT = IN1 * IN2;
 end
endmodule 

乘法比较浪费资源,有些情况下可以用加法来进行代替
例如:
A3 = A2 + A = (A << 1) + A

八位乘法器

//8wei mul
module chengfaqi
(
  IN1,
  IN2,
  OUT   
);
  input signed [7 : 0] IN1, IN2;
  output signed [15 : 0] OUT;
  
  reg signed [15 : 0] OUT; 
  
  always @ (IN1 or IN2) 
    begin 
      OUT = IN1 * IN2;
    end
endmodule 

在这里插入图片描述

十六位乘法器

在这里插入图片描述
通过比较仿真波形,可以明显看出十六位乘法器的输出延迟要比八位乘法器延迟大,并且仿真时间也更长

带D触发器的八位乘法器

//8wei D mul
module chengfaqi
(
  IN1,
  IN2,
  CLK,
  OUT   
);
  input signed [7 : 0] IN1, IN2;
  input CLK;
  output signed [15 : 0] OUT;
  
  reg signed [15 : 0] OUT, mul_out; 
  reg signed [7 : 0] in1_d1R, in2_d1R;
  
  always @ (posedge CLK)
    begin
      in1_d1R <= IN1;
      in2_d1R <= IN2;
      OUT     <= mul_out;
    end
  always @ (in1_d1R or in2_d1R) 
    begin 
      mul_out = in1_d1R * in2_d1R;
    end
endmodule 

RTL电路

在这里插入图片描述
在这里插入图片描述
通过比较八位带D触发器与不带D触发器的波形仿真图,可以观察到毛刺减少。

计数器

module jishuqi
(
  RST,//异步复位,高有效
  CLK,//时钟,上升沿有效
  EN,//输入的计数使能,高有效
  CLR,//输入的清零信号,高有效
  LOAD,//输入的数据加载使能信号,高有效
  DATA,//输入的加载数据信号
  CNTVAL,//输出的计数值信号
  OV//计数溢出信号,计数值为最大值时该信号为1
);
  input RST, CLK, EN, CLR, LOAD;
  input [3: 0] DATA;
  output [3: 0] CNTVAL;
  output OV;
  
  reg [3: 0] CNTVAL, cnt_next;
  reg OV;
  //电路编译参数,最大计数值
  parameter CNT_MAX_VAL = 9;
  
  //组合逻辑,生成cnt_next
  //计数使能最优先,清零第二优先,加载第三优先
  
  always@(EN or CLR or LOAD or DATA or CNTVAL)
  begin
    if(EN)//使能有效
	   begin
		  if(CLR)//清零有效
		    begin
		      cnt_next = 0;	  
		    end
		  else//清零无效
		    begin
			   if(LOAD)//加载有效
				  begin
				    cnt_next = DATA;
				  end
				else//加载无效,正常计数
				//使能有效,清零和加载都无效,根据当前计数值计算下一值
				  begin
				    if(CNTVAL < CNT_MAX_VAL)//未溢出最大值,下一值加一
					   begin
						  cnt_next = CNTVAL + 1'b1;
						end
					 else//计数值达到最大,溢出后cnt_next归零
					   begin
						  cnt_next = 0;
						end
				  end//else LOAD 
		    end//else CLR
			
	  end//if EN
	 else//使能无效,计数值保持不变
	   begin
		  cnt_next = CNTVAL;
		end 

  end
  
  //时序逻辑 更新下一时钟周期的计数值
  //CNTVAL 会被编译为D触发器
  
  always @(posedge CLK or posedge RST)//上升沿触发
    begin
	   if(RST)
		  CNTVAL <= 0;
		else
		  CNTVAL <= cnt_next;
	 end

  //组合逻辑,生成ov
  always @(CNTVAL) 
    begin
	   if(CNTVAL == CNT_MAX_VAL)
		  OV = 1;
		else
		  OV = 0;
	 end

endmodule

状态机

//有限状态机 
//把要解决的问题映射到状态空间,包括:
//定义哪些状态
//状态之间的跳转逻辑是怎样的
//使用表格和状态图来描述状态机的跳转逻辑
//使用parameter定义状态
//尽量使用三段式的标准写法

//本例中,我们要实现一个可乐售卖机的状态机电路,假设可乐3分钱1罐,机器只接受1分钱的硬币,当投入第三个硬币的时候,投出一罐可乐
//Tools-Netlist Viewers-State Machine Viewer查看状态机的状态转移图和表达式


module sanduanshizhuangtaijidaima
(
  CLK,  //clock
  RST,  //
  CENT1IN, //input 1 cent coin
  TINOUT   //output 1 tin cola
);
  
  input CLK;
  input RST;
  input CENT1IN; //输入硬币
  output TINOUT; 
  
  parameter ST_0_CENT = 0;//硬币数量
  parameter ST_1_CENT = 1;
  parameter ST_2_CENT = 2;
  parameter ST_3_CENT = 3;
  
  reg [2-1 : 0] stateR;
  reg [2-1 : 0] next_state;
  reg TINOUT;
  
  //calc next state
  always @ (CENT1IN or stateR)//检测
    begin
      case (stateR)
        ST_0_CENT :     //检测到硬币数量为0
          begin
            if(CENT1IN)//如果继续投硬币,next_state增加一
              next_state = ST_1_CENT;
            else
              next_state = ST_0_CENT;
          end
        ST_1_CENT :    //检测到硬币数量为1
          begin
            if(CENT1IN)
              next_state = ST_2_CENT;
            else
              next_state = ST_1_CENT;
          end
        ST_2_CENT :
          begin
            if(CENT1IN)
              next_state = ST_3_CENT;
            else
              next_state = ST_2_CENT;
          end
        ST_3_CENT :
          begin
            next_state = ST_0_CENT;
          end
        endcase
      end
   
    //state DEF  每个周期进行一次计算,把next_state的值传递给stateR,clk周期很快
    always @ (posedge CLK or posedge RST)
      begin
        if(RST)
		    stateR <= ST_0_CENT;//复位
        else
          stateR <= next_state;
      end
	//???调换顺序?????	
		    
  //calc output
    always @ (stateR)//判断stateR是否满足输出条件
      begin
        if(stateR == ST_3_CENT)
          TINOUT = 1'b1;
        else
          TINOUT = 1'b0;
      end
  
endmodule

识别2进制序列“1011”的状态机

基本要求:
电路每个时钟周期输入1比特数据,当捕获到1011的时钟周期,电路输出1,否则输出0
使用序列101011010作为输出的测试序列
扩展要求:
给你的电路添加输入使能端口,只有输入使能EN为1的时钟周期,才从输入的数据端口向内部获取1比特序列数据。
状态转移图
在这里插入图片描述

module zhuangtaiji(
  CLK,   // clock
  RST,   // reset
  CENT1IN,   // input 1 cent coin
  TINOUT, 
  EN   
  );  // output 1 tin cola

input  CLK; 
input  RST, EN; 
input  CENT1IN; 
output TINOUT;

parameter ST_0_CENT = 0;
parameter ST_1_CENT = 1;
parameter ST_2_CENT = 2;
parameter ST_3_CENT = 3;
parameter ST_4_CENT = 4;

reg [3:0] stateR;
reg [3:0] next_state;
reg         TINOUT;

always @ (CENT1IN or stateR) 

  begin
    case (stateR)
      ST_0_CENT :begin if (CENT1IN  && EN)  next_state = ST_1_CENT ; else next_state = ST_0_CENT; end
      ST_1_CENT :begin if ((CENT1IN == 0) && EN)  next_state = ST_2_CENT ; else next_state = ST_1_CENT; end
      ST_2_CENT :begin if (CENT1IN  && EN)  next_state = ST_3_CENT ; else next_state = ST_0_CENT; end
      ST_3_CENT :begin if (CENT1IN  && EN)  next_state = ST_4_CENT ; else next_state = ST_2_CENT; end
      ST_4_CENT :begin if (CENT1IN  && EN)  next_state = ST_1_CENT ; else next_state = ST_2_CENT; end
      default next_state = ST_0_CENT;
    endcase
  end

// calc output
always @ (stateR) 
begin
  if(stateR == ST_4_CENT)
    TINOUT = 1'b1;
  else 
    TINOUT = 1'b0;
end

// state DFF
always @ (posedge CLK or posedge RST)
begin
  if(RST)
    stateR <= ST_0_CENT;
  else
    stateR <= next_state;
end

endmodule

能够识别“1011”序列的状态机仿真波形
在这里插入图片描述

移位寄存器

//串行数据和并行数据之间的相互转换是在接口设计中很常见的功能,
//一般而言,数据在FPGA内部都是并行传递的,
//当通过串行接口协议(例如SPI,I2C,I2S等)把数据从FPGA内部传送到一个外部芯片
//(例如一片EEPROM存储器或是一片音频DAC)时就需要用到串并转换了,其核心的电路是移位寄存器。


module yiweijicunqi
(
  RST,  //异步复位,高有效
  CLK,  //时钟,上升沿有效
  EN,   //输入数据串行移位使能
  IN,   //输入串行数据
  OUT   //并行输出数据
);

  input RST, CLK, EN; 
  input IN;
  output [3 : 0] OUT;
  reg [3 : 0] shift_R;
  
  assign OUT [3 : 0] = shift_R[3 : 0];
  //时序逻辑,根据输入使能进行串行移位
  // shift_R 会被编译为D触发器
  always @ (posedge CLK or posedge RST) //时钟,上升沿有效 异步复位,高有效 posedge从低到高 从高到低 都会触发
    begin
      if(RST) //异步复位,高有效
        shift_R[3 : 0] <= 0;//清零
      else if(EN)  //异步复位,高有效
		  begin // 串行移位的使能有效
          shift_R[3 : 1] <= shift_R[2 : 0];
          shift_R[0]     <= IN;
        end
      else 
		  begin // 使能无效保持不动
          shift_R[3 : 0] <= shift_R[3 : 0];
        end
   end // always
endmodule

双口RAM

module dpram
(
  WE,
  WCLK,
  RCLK,
  WA,
  RA,
  WD,
  RD
);
  parameter DATAWL = 0;
  parameter ADDRWL = 0;
  parameter C2Q    = 0;
  
  input                 WE, WCLK, RCLK;
  input  [ADDRWL-1 : 0] WA, RA;
  input  [DATAWL-1 : 0] WD;
  output [DATAWL-1 : 0] RD;
  
  reg [DATAWL-1 : 0] RD;
  reg [DATAWL-1 : 0] mem [(1<<ADDRWL)-1 : 0]; ///????
 
  always @ (posedge WCLK)
  begin
    if(WE)
	   mem[WA] <= #C2Q WD;//????
  end 
  
  always @ (posedge RCLK)
  begin
    RD <= #C2Q mem[RA];
  end
  
// ######################################  
// synopsys translate_off                  
// ######################################  
// the code below this line will NOT take part into synthesis
// they are only needed by RTL simulation
  // task DumpDpRAM, get the content of RAM[addr]
  
  task DumpDpRAM;
  input [ADDRWL-1 : 0] addr;
  input [DATAWL-1 : 0] content;
  begin
    content = mem[addr];
  end
  endtask
  
  task RAMInit;
  integer i;
  reg [DATAWL-1 : 0] initData;
  begin
    initData = ‘hAAAA;
	 //initData = (1<<DATAWL) - 1;
	 for(i = 0; i << (1 << ADDRWL); i = i + 1)
	   mem[i] = initData;
  end
  endtask
  
  initial 
  begin
    RAMInit();
	 $display("module dpram().RAMInit()called @ %0d", $time);
  end
// ###################################### 
// synopsys translate_on                  
// ###################################### 
// the code below this line will take part in synthesis
endmodule

ROM

module ROM
(
  CLK,
  RA,
  RD
);

  input CLK;
  input [6 : 0] RA;
  output [12 : 0] RD;
  reg [12 : 0] RD;
  always @ (posedge CLK)
    case(RA)
	   7'd0 : RD = #1 13'd 0;
		7'd1 : RD = #1 13'd 101;
		7'd2 : RD = #1 13'd 201;
		7'd3 : RD = #1 13'd 301;
		... ... ...
		7'd127 : RD = #1 13'd 8190;
	 endcase
endmodule
发布了6 篇原创文章 · 获赞 0 · 访问量 274

猜你喜欢

转载自blog.csdn.net/szm1234/article/details/102551088