【FPDA学习】1-Verilog代码与基本器件学习

 

主要分析老师上课的代码,并完成学生实验。

一、多路选择器

  • 多个输入,一个输出。set相当于选择开关,INX是输入端,out是输出端。

实验要求:做一个4选1的mux,并且进行波形仿真 和2选1的mux对比,观察资源消耗的变化 

// module  top, 选择器(mux)的代码,
module top(
  IN0       ,   // input 1
  IN1       ,   // input 2
  IN2       ,// input 3
  IN3       ,// input 4
  SEL       ,   // select 
  OUT       );  // out data
parameter WL = 16;      // 输入输出数据信号位宽
input [WL-1:0] IN0, IN1,IN2,IN3;// 选择器的4个输入数据信号
input [1:0]SEL;              // 通道选通的控制信号
output[WL-1:0] OUT;     // 选择器的输入数据信号

reg   [WL-1:0] OUT;
// 生成组合逻辑的代码
always @ (IN0 or IN1 or IN2 or IN3 or SEL) begin
  if(SEL==3) // SEL为3 选择输入3
    OUT = IN3;
  else if(SEL==2) // SEL为2 选择输入2
    OUT = IN2;  
  else if(SEL==1) // SEL为1 选择输入1
    OUT = IN1;
  else    // SEL为0 选择输入0
    OUT = IN0;
end
endmodule
// endmodule top

波形仿真

【FPDA学习】1-Verilog代码与基本器件学习

vs 2选1mux 资源消耗更明显

【FPDA学习】1-Verilog代码与基本器件学习

 

二、交叉开关

  • 多个输入,多个输出。

实验要求:编写一个4X4路交叉开关的RTL,然后编译,看RTL View 比较2x2与4x4之间消耗资源的区别。通过对比资源,你有什么结论?

// module  top, a 4x4 crossbar switch circuit

module top(
  IN0       ,   // input 1
  IN1       ,   // input 2
  IN2       ,
  IN3       ,
  SEL0,SEL1,SEL2,SEL3,SEL4,SEL5,SEL6,SEL7,     // select the output source (2^(INnum-1))
  OUT0      ,   // output data 0
  OUT1      ,    // output data 1
  OUT2     ,
  OUT3);  
parameter WL = 16;
input [WL-1:0] IN0, IN1,IN2,IN3;
input SEL0, SEL1,SEL2,SEL3,SEL4,SEL5,SEL6,SEL7;
output[WL-1:0] OUT0, OUT1,OUT2,OUT3;

reg   [WL-1:0] OUT0, OUT1,OUT2,OUT3;
// get the OUT0
always @ (IN0 or IN1 or IN2 or IN3 or SEL0 or SEL1) begin
  if(SEL0)
	if(SEL1)
		OUT0 = IN0;
	else 
		OUT0 = IN1;
  else
	if(!SEL1)
		OUT0 = IN2;
	else 
		OUT0 = IN3;
end
// get the OUT1
always @ (IN0 or IN1 or IN2 or IN3 or SEL2 or SEL3) begin
  if(SEL2)
	if(SEL3)
		OUT1 = IN0;
	else 
		OUT1 = IN1;
  else
	if(!SEL3)
		OUT1 = IN2;
	else 
		OUT1 = IN3;
end
// get the OUT2
always @ (IN0 or IN1 or IN2 or IN3 or SEL4 or SEL5) begin
  if(SEL4)
	if(SEL5)
		OUT2 = IN0;
	else 
		OUT2 = IN1;
  else
	if(!SEL5)
		OUT2 = IN2;
	else 
		OUT2 = IN3;
end
// get the OUT3
always @ (IN0 or IN1 or IN2 or IN3 or SEL6 or SEL7) begin
  if(SEL6)
	if(SEL7)
		OUT3 = IN0;
	else 
		OUT3 = IN1;
  else
	if(!SEL6)
		OUT3 = IN2;
	else 
		OUT3 = IN3;
end
endmodule
// endmodule top

资源消耗对比 2x2 vs 4x4

【FPDA学习】1-Verilog代码与基本器件学习    【FPDA学习】1-Verilog代码与基本器件学习

对比资源消耗发现没有区别(通过查找,发现有如下答案:对比2x2与4x4之间消耗资源,可以发现4x4交叉开关消耗的引脚数是2x2的2倍)

 

三、优先编码器

  • 多个条件同时成立,则按照优先级高的条件输出

实验要求:编写一个8输入的优先编码器,然后编译,看RTL View

// module top, 8 input priority encoder with zero input check
module top(
  IN        ,   // input  
  OUT       );  // output 
input [7:0] IN;
output[3:0] OUT;

reg   [3:0] OUT;
// get the OUT
always @ (IN) begin
   if(IN[7])       // 第一优先
     OUT = 4'b0111;
   else if(IN[6])  // 第二优先
     OUT = 4'b0110;
   else if(IN[5])  // 第三优先
     OUT = 4'b0101;
   else if(IN[4])  // 第四优先
     OUT = 4'b0100;
   else if(IN[3])  // 第5优先
     OUT = 4'b0011;
   else if(IN[2])  // 第6优先
     OUT = 4'b0010;
   else if(IN[1])  // 第7优先
     OUT = 4'b0001;
   else if(IN[0])  // 第8优先
     OUT = 4'b0000;
   else            // 什么都没有检测到
     OUT = 4'b1111; // 输出值可自定义,不和上面的输出值混淆即可
end
endmodule

RTL View

【FPDA学习】1-Verilog代码与基本器件学习

 

四、多路译码器

  • 纯粹的组合逻辑电路
  • 使用case 语句实现,注意,一定要把case的情况写全,或者要加上default

实验内容:编写一个4-16的译码器,编译。和3-8译码器对比资源开销。看RTL View

// module top, 4 input priority encoder with zero input check
module top(
  IN        ,   // input  
  OUT       );  // output 

input [3:0] IN;
output[7:0] OUT;

reg   [7:0] OUT;
// get the OUT
always @ (IN) begin
  case(IN)
    4'b0000: OUT = 16'b0000_0001;
    4'b0001: OUT = 16'b0000_0010;
    4'b0010: OUT = 16'b0000_0011;
    4'b0011: OUT = 16'b0000_0100;
    4'b0100: OUT = 16'b0000_0101;
    4'b0101: OUT = 16'b0000_0110;
    4'b0110: OUT = 16'b0000_0111;
    4'b0111: OUT = 16'b0000_1000;
    4'b1000: OUT = 16'b0000_1001;
    4'b1001: OUT = 16'b0000_1010;
    4'b1010: OUT = 16'b0000_1011;
    4'b1011: OUT = 16'b0000_1100;
    4'b1100: OUT = 16'b0000_1101;
    4'b1101: OUT = 16'b0000_1110;
    4'b1110: OUT = 16'b0000_1111;
    4'b1111: OUT = 16'b0001_0000;
    //  full case 不需要写default,否则一定要有default
  endcase
end
endmodule

资源消耗:3-8 vs 4-16

【FPDA学习】1-Verilog代码与基本器件学习【FPDA学习】1-Verilog代码与基本器件学习

输入多了一位,输出多了8位,资源的开销增长多了一倍,因此资源开销与输出位数正相关。

RTL View:

【FPDA学习】1-Verilog代码与基本器件学习

 

五、加法器

  • 无符号加法器
  • 输入和输出数据都是无符号的整数,常用于计数器累加和计算地址序号

实验要求:(1)把加法器的输出信号改成4比特位宽,编译,波形仿真。观察输出结果,说出输出和输入的对应关系。

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

【FPDA学习】1-Verilog代码与基本器件学习

在只更改输出bit位的情况下,4bit最大输出15,若输入数据相加大于Oct17,无法输出正确的数据值,输出值要考虑最高位溢位。

(2)把加法器的输入信号改成8比特位宽,编译,波形仿真。观察加法器的输出延迟,和4比特输入位宽的情况对比,你有什么结论,为什么?

//8bit输入
module top(
  IN1   ,
  IN2   ,
  OUT   );
input[7:0] IN1, IN2;
output[7:0] OUT;
reg[12:0] OUT;
always@(IN1 or IN2) begin // 生成组合逻辑的always 块
  OUT = IN1 + IN2;
end
endmodule 

加法器的输出延迟:8bit输入 vs 4bit输入

【FPDA学习】1-Verilog代码与基本器件学习

【FPDA学习】1-Verilog代码与基本器件学习

总体来说,8bit输入延迟大于4bit输入延迟。因为8bit输入涉及的位数更多,每一位的延迟叠加,自然会使延迟更大。

 

  • 补码加法器
  • 输入和输出数据都是2补码形式的有符号数,常用于数字信号处理电路
  • EDA工具检测到了"signed"关键字,所以才生成了补码加法器的电路逻辑。

实验要求:(1)把加法器的输出信号改成4比特位宽,编译,波形仿真。观察输出结果,观察输出结果在什么时候是正确的?

//4bit输入补码加法器
module top2(
  IN1   ,
  IN2   ,
  OUT   );
input signed [3:0] IN1, IN2;
output signed [3:0] OUT;
reg signed [3:0] OUT;
always@(IN1 or IN2) begin // 生成组合逻辑的always 块
  OUT = IN1 + IN2;
end
endmodule

【FPDA学习】1-Verilog代码与基本器件学习

输出最高位为符号位,4bit输出范围为:-7~7,当输出值在该范围内可以得到正确结果,超出该范围无法输出。

(2)把加法器的输入信号改成8比特位宽,编译,波形仿真。观察加法器的输出延迟,和4比特输入位宽的情况对比,你有什么结论,为什么?

//8bit输入补码加法器
module top2(
  IN1   ,
  IN2   ,
  OUT   );
input signed [7:0] IN1, IN2;
output signed [7:0] OUT;
reg signed [12:0] OUT;
always@(IN1 or IN2) begin // 生成组合逻辑的always 块
  OUT = IN1 + IN2;
end
endmodule

加法器的输出延迟:8bit输入 vs 4bit输入

【FPDA学习】1-Verilog代码与基本器件学习

【FPDA学习】1-Verilog代码与基本器件学习

两者延迟差别不大。

 

  • 带流水线的加法器
  • 纯粹的加法器是一堆组合逻辑门构成的,这些组合逻辑的计算延迟较大,如果加法器电路的前极或后级电路也是一个规模较大的组合逻辑,那么它们会和加法器电路合并成为一个更大的组合逻辑,从而带来更大的组合逻辑计算延迟。
  • 每一个D触发器都有其所容许的最小的建立与保持时间,当两个D触发器之间的组合电路逻辑延迟变得更大的时候,会导致电路只能工作在更低的时钟频率,为了让电路能够工作在更高的时钟频率,需要用D触发器来把大块的组合逻辑分割为小块,这就是流水线技术。(建议自行Google 关键字 D触发器 建立与保持时间)

实验要求:(1)不改变流水线的级数,把加法器的输入信号改成8比特位宽,编译,波形仿真,和不带流水线的情况对比一下,你有什么结论?

//8bit输入带流水线的加法器
module top(
  IN1   ,
  IN2   ,
  CLK   ,
  OUT   );
input  [3:0] IN1, IN2;
input CLK;
output  [4:0] OUT;
reg [3:0] in1_d1R, in2_d1R;
reg  [4:0] adder_out, OUT;
always@(posedge CLK) begin // 生成D触发器的always块
  in1_d1R <= IN1;
  in2_d1R <= IN2;
  OUT     <= adder_out;
end
always@(in1_d1R or in2_d1R) begin // 生成组合逻辑的always 块
  adder_out = in1_d1R + in2_d1R;
end
endmodule 

波形仿真:

clk=1/0时,加法器没有启动。

【FPDA学习】1-Verilog代码与基本器件学习

【FPDA学习】1-Verilog代码与基本器件学习

带流水线 vs 不带流水线

【FPDA学习】1-Verilog代码与基本器件学习

【FPDA学习】1-Verilog代码与基本器件学习

带流水线后波形仿真毛刺减少了。

(2)在8比特输入位宽的情况下,在输入上再添加一级流水线,观察编译和仿真的结果,你有什么结论?

//8bit输入带两个流水线的加法器
module top3(
  IN1   ,
  IN2   ,
  CLK   ,
  OUT   );
input  [7:0] IN1, IN2;
input CLK;
output  [8:0] OUT;
reg [7:0] in1_d1R, in2_d1R,in1_d2R, in2_d2R;
reg  [8:0] adder_out, OUT;
always@(posedge CLK) begin // 生成D触发器的always块
  in1_d1R <= IN1;
  in2_d1R <= IN2;
  in1_d2R <= in1_d1R;
  in2_d2R <= in2_d1R;
  OUT     <= adder_out;
end
always@(in1_d1R or in2_d1R) begin // 生成组合逻辑的always 块
  adder_out = in1_d2R + in2_d2R;
end
endmodule 

 【FPDA学习】1-Verilog代码与基本器件学习

流水线的级数越高,毛刺也随之越短,但输出的时延也会相应的多一个时钟周期。

 

六、乘法器

  • 乘法器是一种奢侈品会消耗大量的组合电路逻辑资源,一定要慎重使用。
  • 乘法器的代码和加法器类似,有无符号,有符号的乘法器,另外还可以添加流水线。
  • 有时可以用加法来替代乘法,例如 : A*3 = A*2 + A = (A << 1) + A  

实验内容:

(1)改变乘法器的输入位宽为8比特,编译,波形仿真,观察信号毛刺的时间长度。

  8bit输入无符号的乘法器  /
module top(
  IN1   ,
  IN2   ,
  OUT   );
 input [7:0] IN1, IN2;
 output [15:0] OUT;
 reg [15:0] OUT;
always@(IN1 or IN2) begin // 生成组合逻辑的always 块
  OUT = IN1 * IN2;
end
endmodule 

【FPDA学习】1-Verilog代码与基本器件学习

可以发现信号毛刺很多,延时较长。 

(2)选一款没有硬件乘法器的FPGA芯片(例如Cyclone EP1C6)对比8比特的乘法器和加法器两者编译之后的资源开销(Logic Cell的数目)

乘法器 vs 加法器

【FPDA学习】1-Verilog代码与基本器件学习 【FPDA学习】1-Verilog代码与基本器件学习

可以发现乘法器的资源开销比加法器大很多。

(3)编写一个输入和输出都有D触发器的流水线乘法器代码,编译后波形仿真,观察组合逻辑延迟和毛刺的时间,和不带流水线的情况下对比。

  输入和输出都有D触发器的流水线乘法器  /
module top(
  IN1   ,
  IN2   ,
  CLK   ,
  OUT   );
input  [7:0] IN1, IN2;
input CLK;
output  [8:0] OUT;
reg [7:0] in1_d1R, in2_d1R;
reg  [8:0] adder_out, OUT;
always@(posedge CLK) begin // 生成D触发器的always块
  in1_d1R <= IN1;
  in2_d1R <= IN2;
  OUT     <= adder_out;
end
always@(in1_d1R or in2_d1R) begin // 生成组合逻辑的always 块
  adder_out = in1_d1R * in2_d1R;
end
endmodule 

 【FPDA学习】1-Verilog代码与基本器件学习

可以发现毛刺缩短。

 

七、计数器

实验内容:请完成以下设计实验,编译电路并且进行波形仿真。

(1)设计一个最简单的计数器,只有一个CLK输入和一个OVerflow输出,当计数到最大值的时钟周期CLK输出1

 最简单的计数器代码  /

module top(
  CLK   , // 时钟,上升沿有效
  CNTVAL,
  OV  );// 计数溢出信号,计数值为最大值时该信号为1

input CLK;
output [2:0] CNTVAL;
output OV;   

reg [2:0] CNTVAL, cnt_next;
reg OV;
// 电路编译参数,最大计数值
parameter CNT_MAX_VAL = 7;

always @ (posedge CLK ) begin
	CNTVAL <= CNTVAL + 1;
end

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

endmodule

【FPDA学习】1-Verilog代码与基本器件学习

(2)设计复杂的计数器,和本例相似,带有多种信号,其中同步清零CLR的优先级最高,使能EN次之,LOAD最低。

 计数器代码  同步清零CLR的优先级最高,使能EN次之,LOAD最低  /

module top(
  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
// 同步清零CLR的优先级最高,使能EN次之,LOAD最低
always @(EN or CLR or LOAD or DATA or CNTVAL) begin
  if(CLR) 
	cnt_next = 0;
  else if(EN) 
	if(LOAD)
		cnt_next = DATA;
    else if(CNTVAL < CNT_MAX_VAL) 
        cnt_next = CNTVAL + 1'b1;
    else  
        cnt_next = 0;
  else
	cnt_next = CNTVAL;
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

【FPDA学习】1-Verilog代码与基本器件学习

 

八、状态机 

  • 有限状态机(Finite State Machine)同样是数字电路设计中非常常用的模块,其在EDA设计中的地位等同于C语言中的If-else语句。

实验要求:设计一个用于识别2进制序列“1011”的状态机

           基本要求:电路每个时钟周期输入1比特数据,当捕获到1011的时钟周期,电路输出1,否则输出0;

                             使用序列101011010作为输出的测试序列

           扩展要求:给你的电路添加输入使能端口,只有输入使能EN为1的时钟周期,才从输入的数据端口向内部获取1比特序列数据。

  状态机代码  /
module top(
  CLK       ,   // clock
  RST       ,   // reset
  EN        ,
  IN        ,
  TINOUT    );  // output 1 tin cola

input  CLK       ; 
input  RST       ; 
input  EN,IN  ; 
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 [4-2:0]stateR       ;
reg [4-2:0]next_state   ;
reg        TINOUT       ;

// calc next state
always @ (IN or stateR or EN) begin
  if(EN)begin
  case (stateR)
    ST_0_CENT :begin if(IN) next_state = ST_1_CENT ; else next_state = ST_0_CENT; end
    ST_1_CENT :begin if(!IN) next_state = ST_2_CENT ; else next_state = ST_0_CENT; end
    ST_2_CENT :begin if(IN) next_state = ST_3_CENT ; else next_state = ST_0_CENT; end
    ST_3_CENT :begin if(IN) next_state = ST_4_CENT ; else next_state = ST_0_CENT; end
    ST_4_CENT :begin next_state = ST_0_CENT; end
  endcase
  end
  else
	next_state <= ST_0_CENT;
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
  • Quartus中,编译代码之后,可以在Tools-Netlist Viewers-State Machine Viewer 里面看到你写的状态机的状态转移图和表达式 

【FPDA学习】1-Verilog代码与基本器件学习

【FPDA学习】1-Verilog代码与基本器件学习

 

九、移位寄存器

实验要求:设计一个如本节“电路描述”部分的“带加载使能和移位使能的并入串出”的移位寄存器,电路的RTL结构图如“电路描述”部分的RTL结构图所示。 

【FPDA学习】1-Verilog代码与基本器件学习

 带加载使能和移位使能的并入串出寄存器  /
module top(
  RST   ,   // 异步复位, 高有效
  CLK   ,   // 时钟,上升沿有效
  EN    ,   // 输入数据串行移位使能
  LOAD  ,   //加载使能
  IN    ,   // 输入并行电路
  OUT   );  // 串行输出数据

input RST, CLK, EN,LOAD;
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 or posedge LOAD) begin
  if(RST) 
    shift_R[3:0] <= 0;
  else
    if(LOAD) begin 
	  shift_R[3:0] <=IN[3:0];end
	else begin
	  if(EN) begin
		shift_R[3:1] <= shift_R[2:0];
		shift_R[0]   <= 0;
		OUT = shift_R[3];
    end
    else begin // 使能无效保持不动
      shift_R[3:0] <= shift_R[3:0];
    end
end // always
endmodule

 

上一篇:【Sublime】Ubuntu下为 Sublime 配置 Verilog 语法检查插件


下一篇:基于Verilog HDL的数字秒表