目录

一、多路选择器

学生实验

二、交叉开关

学生实验:

三、优先编码器

四、多路译码器

五、加法器

1.无符号加法器

学生实验:

2.补码加法器

3.带流水线的加法器

学生实验:

六、乘法器

学生实验

 七、计数器

学生实验:

 八、状态机

 学生实验:

九、移位寄存器

学生实验:


一、多路选择器

2选1mux电路结构

学生实验

使用case语句写的4选1mux

使用输入端口更多,选择信号的宽度改变,使得使用的资源变多,电路变得复杂很多(电路截图只截取了一部分)

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

reg   [16-1:0] OUT;
// 生成组合逻辑的代码
always @ (IN0 or IN1 or IN2 or IN3 or SEL) begin
  case(SEL)
	2'b00: OUT = IN0;
	2'b01: OUT = IN1;
	2'b10: OUT = IN2;
	2'b11: OUT = IN3;
  endcase
end
endmodule

只有total pins项发生改变 

 


二、交叉开关

从代码和电路可以看出:交叉开关实质是多路选择器的组合,每一个输出端都有对应的选通信号

学生实验:

4×4交叉开关,相当于4个4选1的选择器集合,代码使用了if-else语句

module top (IN0,IN1,IN2,IN3,
			SEL0,SEL1,SEL2,SEL3,
			OUT0,OUT1,OUT2,OUT3);
parameter WL = 16;
input [WL-1:0] IN0,IN1,IN2,IN3;
input SEL0,SEL1,SEL2,SEL3;
output [WL-1:0]OUT0,OUT1,OUT2,OUT3;

reg [WL-1:0]OUT0,OUT1,OUT2,OUT3;

// GEI THE OUT0
always @ (IN0 or IN1 or IN2 or IN3 or SEL0 or SEL1)
begin
  if ((SEL0)&&(SEL1))
		OUT0=IN3;
	else if ((!SEL0)&&(SEL1))
		OUT0=IN2;
	else if((SEL0)&&(!SEL1))
		OUT0=IN1;
	else
		OUT0 = IN0;
end

always @ (IN0 or IN1 or IN2 or IN3 or SEL1 or SEL2)
begin
  if ((SEL1)&&(SEL2))
		OUT1=IN3;
	else if ((!SEL1)&&(SEL2))
		OUT1=IN2;
	else if((SEL1)&&(!SEL2))
		OUT1=IN1;
	else
		OUT1= IN0;
end

always @ (IN0 or IN1 or IN2 or IN3 or SEL2 or SEL3)
begin
  if ((SEL2)&&(SEL3))
		OUT2=IN3;
	else if ((!SEL2)&&(SEL3))
		OUT2=IN2;
	else if((SEL2)&&(!SEL3))
		OUT2=IN1;
	else
		OUT2= IN0;
end
	 
always @ (IN0 or IN1 or IN2 or IN3 or SEL3 or SEL0)

begin
  if ((SEL3)&&(SEL0))
		OUT3=IN3;
	else if ((!SEL3)&&(SEL0))
		OUT3=IN2;
	else if((SEL3)&&(!SEL0))
		OUT3=IN1;
	else
		OUT3 = IN0;
end
endmodule

下面是两个电路仿真的RTL视图和资源对比,端口增加使得消耗资源增加

2×2交叉开关 

4×4交叉开关


三、优先编码器

  • 常用于状态检测
  • 优先的含义是,如果多个条件同时成立,则按照优先级高的条件输出
  • 例如,CPU的中断编码电路就是一个优先编码器

没有很多特别之处,学生实验的代码也只是做类似的扩展即可(左侧是4比特输入,右侧是8比特输入)

四、多路译码器

多路译码器用处非常广泛,代码中使用了case语句,需要注意格式,4-16译码器比起3-8译码器多管脚,消耗的资源也更多了

因为系统有DECODER模块,RTL视图比较简洁

五、加法器

加法器是很常用的电路,有无符号加法器和补码加法器两种形式。

1.无符号加法器

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

波形仿真图中输入数据和对应结果在时间上是有延迟的,这是组合逻辑门的延迟造成的。
输出结果有毛刺,这是因为输入信号不止1比特,无法做到同时翻转。

本例中输出比输入多一个比特,这保证了进位不会丢失,保证了结果是正确的。(截图中第一个输入是15+15,结果中30是正确的,这是需要输出多1比特才能保证正确)

学生实验:

输出信号改成4比特位宽会使结果中的最高位进位丢失,结果可能是不正确的。如15+15的结果是14,发生了错误。

输入信号改成8比特位宽后,输出延时依然在10ns左右,没有明显变化,说明延迟与管脚数量没有关系。

2.补码加法器

输入和输出的数据都是2补码形式的有符号数,常用于数字信号处理电路

截图的上面是无符号加法器波形,下面是补码加法器波形,可以看出,两个波形的延迟和毛刺肉眼看来没有太大差别

把进制改成无符号十进制发现计算结果是不正确的,补码加法器是不可以作为输入多出1比特时的无符号加法器使用的。

波形图上的数据是带负号的,这是通过选择信号-属性-Radix为 有符号的十进制 来观察的结果 请思考,对于同样的二进制比特数据,我们可以用不同的Radix观察它,但注意信号的Radix设置必须符合其本质,才能显示正确的波形。

把加法器输出改4比特位宽,可以看到同样出现了错误

把输入改8比特位宽,没有发现延时和毛刺有大的变化

3.带流水线的加法器

带流水线的加法器即在加法器的输入与输出都连接了D触发器,D触发器可以通过时钟同步,有效的减少了组合逻辑的竞争与冒险,从而减少毛刺。从波形仿真可以看出来,毛刺的长度明显减少,但输入与对应的结果是不在一个周期的。

8比特带宽加法器加流水线后也减少了毛刺的长度,但延时没有大的变化。

学生实验:

对无符号加法器加两次流水线后,RTL视图中可以看到多出一层D触发器,波形仿真中对应结果的延时比一层流水线时的延时更长

module top(
  IN1   ,
  IN2   ,
  CLK   ,
  OUT   );
input  [3:0] IN1, IN2;
input CLK;
output  [4:0] OUT;
reg [3:0] in01_d1R, in02_d1R;
reg [3:0] in11_d1R, in12_d1R;
reg  [4:0] adder1_out,adder0_out, OUT;
always@(posedge CLK) begin // 生成D触发器的always块
  in01_d1R <= IN1;
  in02_d1R <= IN2;
  adder1_out<= adder0_out;
end
always@(posedge CLK) begin // 生成D触发器的always块
  in11_d1R<= in01_d1R;
  in12_d1R<= in02_d1R;
  OUT     <= adder1_out;
end
always@(in11_d1R or in12_d1R) begin // 生成组合逻辑的always 块
  adder0_out = in11_d1R + in12_d1R;
end
endmodule 

六、乘法器

乘法器是一种奢侈品会消耗大量的组合电路逻辑资源,一定要慎重使用

乘法器的代码和加法器类似,有无符号,有符号的乘法器,另外还可以添加流水线。

1.符号位

对于补码乘法器,计算结果会扩展出1比特额外的符号位,从波形图中可以看到,最高2个比特总是一样的。

 那么为什么代码不使用更少的比特呢?我把输出减少1比特后,却发现-8×-8出现错误

 然后使用同样的输入,输出8比特结果,发现,当输入是-8×-8时,输出的最高两位的确是不同的

但是在实际使用中会避免产生-8这样的数字。

2.资源消耗

使用默认的芯片(Stratix II)是有乘法器的,改用芯片Cyclone EP1C6F256C7,对比8比特的乘法器和加法器两者编译之后的资源开销,可以看出乘法器的资源使用是比加法器多的。(左侧是乘法器)

3.输入改为8比特带宽后,波形仿真的毛刺没有太大变化

学生实验

对乘法器加入流水线后,会发现毛刺变少

module top(
  IN1   ,
  IN2   ,
  CLK	,
  OUT   );
 input signed [7:0] IN1, IN2;
 input CLK;
 output signed [15:0] OUT;
 
 reg signed [15:0] muler_out,OUT;
 reg signed [7:0] in1_dir,in2_dir;
 
always@(posedge CLK) begin // 生成组合逻辑的always 块
  in1_dir<=IN1;
  in2_dir<=IN2;
  OUT<=muler_out;
end

always @ (in1_dir or in2_dir) begin
	muler_out=in1_dir * in2_dir;
end
endmodule 

没有流水线

 有流水线,毛刺变少

 

 七、计数器

计数器是最为常用的时序电路之一

最简单的计数器只有一个CLK信号和一个计数值Q信号,在实际应用中的计数器可能会出现较多的变化,例如下图所示的各种端口,完成不同的逻辑功能,列举如下:

  • 计数溢出功能,当计数到某个最大值MAX的时候,OV(OVerflow)信号输出1,否则输出0
  • 计数使能功能,当输入使能EN信号为1的周期,计数器的Q值会有更新的动作,否则保持不动
  • 计数同步清零功能,当输入的CLR信号为1的周期,在下一个周期Q端清零。
  • 计数同步加载功能,当输入的LOAD信号为1的周期,DATA信号被选通至触发器的D端,在下一个周期传递至Q端。
  • 以上的功能是有重叠的,根据实际应用不同,EN信号、CLR信号、LOAD信号的优先级可能会不同,例如某个电路需要在EN无效的时候D触发器彻底被锁死,清零信号和同步加载信号的值对D触发器没有影响,而另一个电路可能会要求清零信号的优先级更高。实际应用中,需要根据不同的需求用不同的代码来描述(主要是用if-else的嵌套,参考上文中的优先编码器例子)。这正是利用代码来描述电路的优势所在。

这个Verilog程序的功能定义中:组合逻辑是定义各个功能及其优先级,生成OV;时序逻辑是更新下一时钟周期的计数值,会编译为D触发器。

老师所给的代码中有严谨的使用很多begin-end,我尝试去掉begin-end,发现在if-else语句只有一句时是可以直接写begin-end不会出现错误的。

 计数器代码  /

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
// 计数使能最优先,清零第二优先,加载第三优先
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 // 未计数到最大值, 下一值加1
          cnt_next = CNTVAL + 1'b1;
        end
        else begin // 计数到最大值,下一计数值为0
          cnt_next = 0;
        end
      end // else LOAD
    end  // else CLR
  end // if EN
  else begin  // 使能无效,计数值保持不动
    cnt_next = CNTVAL;
  end // else EN
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

 

学生实验:

简单版只需要有输入时钟信号,输出溢出信号

 

复杂版:调整优先级也就是调整if-else语句。其中同步清零CLR的优先级最高,使能EN次之,LOAD最低。代码中if的条件判断顺序就是CLR,EN,LOAD,从RTL视图可以看出来这三个功能控制的选择器位置是相对应的(与原来的RTL视图对比也可以看出改变)。

 

 八、状态机

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

 设计要点:把要解决的问题映射到状态空间

包括:

  • 定义哪些状态
  • 状态之间的跳转逻辑是怎样的
  • 使用表格和状态图来描述状态机的跳转逻辑
  • 使用parameter定义状态
  • 尽量使用三段式的标准写法

 RTL视图并没有得到参考图中的样子

 学生实验:

画出状态图后改写参考代码中状态转换的部分,把使能en放在状态转换的语句附近。

网上查找到的答案是不正确的,从1010的第二个‘0’就转移回了状态0,即从状态3之间回到状态0,这样在遇到‘101011’时无法识别出1011。
更改为在状态3下输入0转移到状态2,才可以完全识别到1011.

module teller1011(clk,rst,in,en,out);

input clk;
input rst;
input in;
input en;
output out;

parameter ST_0= 0;
parameter ST_1= 1;
parameter ST_2= 2;
parameter ST_3= 3;
parameter ST_4= 4;

reg [2:0] stateR;
reg [2:0] next_state;
reg 	  out;

always @(in or stateR or en)
begin
	if(en)
	begin
	case(stateR)
		ST_0 :begin if(in) next_state = ST_1 ; else next_state = ST_0; end
		ST_1 :begin if(!in) next_state = ST_2 ; else next_state = ST_0 ; end
		ST_2 :begin if(in) next_state = ST_3 ; else next_state = ST_0 ; end
		ST_3 :begin if(in) next_state = ST_4 ; else next_state = ST_2 ; end
		ST_4 :begin next_state = ST_0 ; end
	endcase
	end
	else
	next_state<=ST_0;
end

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

// state DFF
always @ (posedge clk or posedge rst)begin
  if(rst)
    stateR <= ST_0;
  else
    stateR <= next_state;	
end
endmodule

 

图中有4个红框,前两个是识别出1011序列的高电平输出;第三个框:en为0时,输入的信号无效,第三个框里面无法识别1011;第四个框:rst为1,状态清0,本来的1011无法识别 。

九、移位寄存器

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

参考代码可以成功实现串入并出

 串入并出移位寄存器  /
module top(
  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) 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

学生实验:

并入串出寄存器

input en;
input [3:0] in;
input load;
output out;

reg [3:0] shift;
//reg dout;

always @(posedge clk)
begin
	if(!en)
		shift<=shift;
	else if(!load)
			shift={shift[2:0],1'b0};
		else
			shift<=in;
end

assign out=shift[3];

endmodule

更多推荐

作业01——Verilog RTL代码新手上路教程