Verilog RTL基础模块代码设计学习笔记

Verilog RTL 基础模块代码设计

  • 组合逻辑电路
    • 多路选择器
      • 电路描述
      • 2选1的mux
      • 4选1的mux
    • 交叉开关
      • 电路描述
      • 2x2路交叉开关
      • 4x4路交叉开关
    • 优先编码器
      • 电路描述
      • 4_2优先编码器
      • 8_3优先编码器
    • 多路译码器
      • 电路描述
      • 3_8译码器
      • 4_16译码器
    • 加法器
      • 无符号加法器
        • 仿真分析
        • 输入输出位宽分析
      • 补码加法器
      • 带流水线的加法器
        • 一层流水线加法器
        • 两层流水线8bit加法器
    • 乘法器
      • 电路描述
      • 乘法器的特点
  • 时序逻辑电路
    • 计数器
      • 电路描述
      • 最简单的模4计数器
      • 含有多种控制信号的计数器
    • 状态机
      • 电路描述
      • 三段式状态机
      • 序列信号检测器
    • 移位寄存器
      • 电路描述
      • 串入并出移位寄存器
      • 并入串出移位寄存器

组合逻辑电路

 

多路选择器

电路描述

  • 纯组合逻辑
  • 根据控制信号的值,把输入信号之一连接到输出信号上。
  • 可能的变化
    • 数据信号的宽度
    • 选通逻辑的变化,控制信号为0或1时选通哪个通道

 

2选1的mux

module mux2_1(
  IN0       , 
  IN1       ,
  SEL       ,
  OUT       );
parameter WL = 1;      // bit wide of input and output. set 1 for conciseness here
input [WL-1:0] IN0, IN1;
input SEL;
output[WL-1:0] OUT;

reg   [WL-1:0] OUT;
// code for combinational logic
always @ (IN0 or IN1 or SEL) begin
  if(SEL)
    OUT = IN1;
  else
    OUT = IN0;
end
endmodule
// endmodule top

小声:本人英语拉得很,但是我的quartus9.1不支持中文,所以所有代码注释均为英文。如有离谱之处,敬请指出。(当然代码如有问题还望不吝赐教)

编译报告:
Verilog RTL基础模块代码设计学习笔记_第1张图片
编译报告要注意一些资源的消耗,主要有:

  • logic elements 逻辑单元
  • logic registers 逻辑寄存器,主要用于设置D触发器
  • memory bits 所用的内存单元,ram等
  • multiplier 所到的乘法器
  • PLLs 锁相环

仿真结果:
Verilog RTL基础模块代码设计学习笔记_第2张图片
电路中的逻辑单元有延迟,故输出有延迟,所以时钟间隔应当大一些。我一般设置为100ns。

 

4选1的mux

module mux4_1(
	IN0		,
	IN1		,
	IN2		,
	IN3		,	//4 input
	SEL		,
	OUT		);
parameter 	WL = 1;		//input bit wide
input 	[WL-1:0] 	IN0,IN1,IN2,IN3;
input 	[1:0]		SEL;	//4input need 2bits select
output 	[WL-1:0] 	OUT;

reg		[WL-1:0]	OUT;

always @ (IN0 or IN1 or IN2 or IN3 or SEL) begin
	case(SEL)
		0		:	OUT = IN0;
		1		:	OUT = IN1;
		2		:	OUT = IN2;
		default	:	OUT = IN3;
	endcase
end
endmodule

注意case后面要有end。最好写完case( )end再填充内容,防止忘记。

编译报告:
Verilog RTL基础模块代码设计学习笔记_第3张图片
与2选1的mux编译报告对比可发现4选1的mux资源用了更多的逻辑单元。

仿真结果:
4选1的mux仿真波形
输出正确。

 

交叉开关

电路描述

  • 实际上是MUX的组合体
  • 通常用在复杂一些的信号选通的场合。
  • 每一个输出端都有对应的选通信号
  • 用选通信号控制输出端选通到哪个输入端。
  • 当输入和输出的端口增加时,该电路会消耗非常多的电路资源
  • 电路在不同应用时的变化
    • 数据信号的宽度
    • 选通逻辑的变化,选择信号为0或1时选通哪个通道

 

2x2路交叉开关

// module  top, a 2x2 crossbar switch circuit

module crossbar_switch_2x2(
  IN0       ,   // input 1
  IN1       ,   // input 2
  SEL0      ,   // select the output0 source 
  SEL1      ,   // select the output1 source 
  OUT0      ,   // output data 0
  OUT1      );  // output data 1
parameter WL = 1;
input [WL-1:0] IN0, IN1;
input SEL0, SEL1;
output[WL-1:0] OUT0, OUT1;

reg   [WL-1:0] OUT0, OUT1;
// get the OUT0
always @ (IN0 or IN1 or SEL0) begin
  if(SEL0)
    OUT0 = IN1;
  else
    OUT0 = IN0;
end
// get the OUT1
always @ (IN0 or IN1 or SEL1) begin
  if(SEL1)
    OUT1 = IN1;
  else
    OUT1 = IN0;
end
endmodule
// endmodule top

代码中不难看出这里其实是两个多路选择器的组合。

RTL Viewer:
Verilog RTL基础模块代码设计学习笔记_第4张图片
RTL视图里也可以清晰地看到这其实只是两个多路选择器的组合。

 

4x4路交叉开关

module crossbar_switch_4x4(
	IN0		,
	IN1		,
	IN2		,
	IN3		,
	SEL0	,
	SEL1	,
	SEL2	,
	SEL3	,
	OUT0	,
	OUT1	,
	OUT2	,
	OUT3	);
parameter WL = 1;
input	[WL-1:0]	IN0,IN1,IN2,IN3;
input	[1:0]		SEL0,SEL1,SEL2,SEL3;
//Each output need a independent select signals, so we need 4 sel;
//There are 4 inputs, so sel need 2 bis. 
output	[WL-1:0]	OUT0,OUT1,OUT2,OUT3;

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

always @ (IN0 or IN1 or IN2 or IN3 or SEL0) begin
	case(SEL0)
		0		:	OUT0 = IN0;
		1		:	OUT0 = IN1;
		2		:	OUT0 = IN2;
		default	:	OUT0 = IN3;
	endcase
end

always @ (IN0 or IN1 or IN2 or IN3 or SEL1) begin
	case(SEL1)
		0		:	OUT1 = IN0;
		1		:	OUT1 = IN1;
		2		:	OUT1 = IN2;
		default	:	OUT1 = IN3;
	endcase
end

always @ (IN0 or IN1 or IN2 or IN3 or SEL2) begin
	case(SEL2)
		0		:	OUT2 = IN0;
		1		:	OUT2 = IN1;
		2		:	OUT2 = IN2;
		default	:	OUT2 = IN3;
	endcase
end

always @ (IN0 or IN1 or IN2 or IN3 or SEL3) begin
	case(SEL3)
		0		:	OUT3 = IN0;
		1		:	OUT3 = IN1;
		2		:	OUT3 = IN2;
		default	:	OUT3 = IN3;
	endcase
end

endmodule

这里的输入输出端口比较多,本来想把input,output,sel全都使用数组表示的,但是报错了。查询语法后发现reg内存的可以只用数组表示,端口不能。用到的四个reg这里可以直接与输出端口匹配,为了防止未知的语法问题和代码复杂化,就没有采用数组表示。

RTL Viewer:
Verilog RTL基础模块代码设计学习笔记_第5张图片
四个输出,分别用四个2bit的sel来分别选择输入信号。也就是四个4_1多路选择器的组合。

 

优先编码器

电路描述

  • 纯粹的组合逻辑电路
  • 常用于状态检测
  • 优先的含义是,如果多个条件同时成立,则按照优先级高的条件输出
  • 使用if-else if 语句实现
  • 例如,CPU的中断编码电路就是一个优先编码器
  • 电路随应用场景的变化
    • 编码信号的个数
    • 编码的逻辑

 

4_2优先编码器

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

reg		[2:0]	OUT;

always @ (IN) begin
	if(IN[3])       // highest priority
		OUT = 3'b011;
	else if(IN[2])  // second priority
		OUT = 3'b010;
	else if(IN[1])  // third priority
		OUT = 3'b001;
	else if(IN[0])  // fourth priority
		OUT = 3'b000;
	else            // detected nothing
		OUT = 3'b111; // arbitrary value which different from the values above
end
endmodule

仿真结果:
4_2优先编码器仿真波形
RTL Viewer:
Verilog RTL基础模块代码设计学习笔记_第6张图片
层级依次选通。

 

8_3优先编码器

module priority_encoder8_3(
	IN	,
	OUT	);
input	[7:0]	IN;
output	[3:0]	OUT;

reg		[2:0]	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])	OUT = 4'b0011;
	else if(IN[2])	OUT = 4'b0010;
	else if(IN[1])	OUT = 4'b0001;
	else if(IN[0])	OUT = 4'b0000;
	else			OUT = 4'b1111;
end

endmodule

仿真结果:
Verilog RTL基础模块代码设计学习笔记_第7张图片
RTL Viewer:
Verilog RTL基础模块代码设计学习笔记_第8张图片
也是依次选通,共8-1=7次。

 

多路译码器

电路描述

  • 纯粹的组合逻辑电路
  • 使用case 语句实现,注意,一定要把case的情况写全,或者要加上default
  • 电路随应用场景的变化
    • 编码信号的位宽
    • 编码的逻辑

 

3_8译码器

// module top, 4 input priority encoder with zero input check
module decoder3_8(
	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;
    //  full case don't need default. otherwise there must have default
  endcase
end
endmodule

编译报告:
Verilog RTL基础模块代码设计学习笔记_第9张图片
共使用逻辑单元8个来组成译码器。

RTL Viewer:
Verilog RTL基础模块代码设计学习笔记_第10张图片
 

4_16译码器

module decoder4_16(
	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;
	endcase
end

endmodule

编译报告:
Verilog RTL基础模块代码设计学习笔记_第11张图片
共使用逻辑单元16个,相当于两个3_8译码器,功能上也可以由两个3_8译码器组合而成,合理。

RTL Viewer:
Verilog RTL基础模块代码设计学习笔记_第12张图片

 

加法器

加法器是很常用的电路,主要常用的形式包括:

  • 无符号加法器:
    输入和输出数据都是无符号的整数,常用于计数器累加和计算地址序号
  • 补码加法器:
    输入和输出数据都是2补码形式的有符号数,常用于数字信号处理电路

 

无符号加法器

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

module unsigned_adder1(
	IN1	,
	IN2	,
	OUT	);
input	[3:0]	IN1, IN2;
output	[4:0]	OUT;
reg		[4:0]	OUT;

always @(IN1 or IN2) begin
	OUT = IN1 + IN2;
end

endmodule 
仿真分析

仿真结果:
四位无符号全加器仿真波形
显然,加法器的输出并不是与输入每一时刻都对应的。输入改变时输出会有延迟,约7.5ns。
Verilog RTL基础模块代码设计学习笔记_第13张图片
上图为0跳变至3,低位为00跳变至11。延迟为7.395ns。
可见:输出不同位的跳变不是同时的,不能做到同时改变,所以会产生过渡的输出状态。共有一个过渡状态。
Verilog RTL基础模块代码设计学习笔记_第14张图片

上图为0跳变至2,低位为00跳变至10。延迟为7.782ns。
如果是10+00,则输出只有一位产生跳变,故没有过渡状态。但若是01+01,则会有进位的过程。先是低位翻转,因为进位低位又翻转回来,然后高位再翻转得到最后的结果。所以进位会增加过渡状态的数目,且增加了延迟时间。

输入输出位宽分析

注意一开始为输入4位输出5位。输出5位因为加法会产生进位,多一位储存进位。若输出设置为4为,即不储存进位,则在计算有进位的式子时会产生错误。
四位无符号半加器仿真波形
可见:无进位时输入与输出相匹配,有进位时输出结果比正确结果小16,即10000B。

输入改为8bit时:
Verilog RTL基础模块代码设计学习笔记_第15张图片
图示电平翻转过程中延迟达到了10.195ns。
翻转时间增加是因为8bit的加法器组合逻辑层级更多,所以延迟更高。但主要延迟还是在输入的高低电平翻转,即图中17.4us至17.408604us的8.604ns,所以延迟增加不是特别大。

 

补码加法器

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

module complement_adder1(
	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 

从组合逻辑门的层面而言,补码加法器和无符号加法器的电路逻辑是有较大区别的。代码区别不大是由于EDA工具检测到了"signed"关键字,所以才生成了补码加法器的电路逻辑。
注意补码加法器的输入数据和对应结果在时间上同样是有延迟的,这是组合逻辑的延迟。此处延迟和无符号加法器差不多。
波形图上的数据是带负号的,这是通过选择信号-属性-Radix为 ‘SIgnal Decimal / 有符号的十进制’ 来观察的结果。这说明电路中有的只是高低电平,我们可以根据自己的需要来选择它所代表的含义。

4bit输入输出时的补码加法器
如果输出的位数和输出的位数相同,当结果不超过输出位数可以表示的时候,即同号相加最高位未产生进位借位时,结果正确。在本例中4bit输出可以表示的范围为-8~7,超过范围可以通过±16来修正得到输出结果,当然修正过的结果是错误的。

8bit输入时也与无符号加法器类似,延时略有增加。

 

带流水线的加法器

与一般加法器的区别:

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

 

一层流水线加法器

引入流水线也就是引入了时序逻辑的部分。
一层流水线指输入,输出级寄存器不算一级流水线。

module pipelined_adder1(
	IN1	,
	IN2	,
	CLK	,
	OUT	);
input	[3:0]	IN1, IN2;
output	[4:0]	OUT;
input	CLK;

reg		[3:0]	in1_d1R, in2_d1R;
reg		[4:0]	adder_out, OUT;

always @ (posedge CLK) begin // diff
	in1_d1R	<= IN1;
	in2_d1R	<= IN2;
	OUT		<= adder_out;
end

always @ (in1_d1R or in2_d1R) begin // combinational logic
	adder_out = in1_d1R + in2_d1R;
end

endmodule

RTL Viewer:
Verilog RTL基础模块代码设计学习笔记_第16张图片

仿真波形:
带流水线的加法器仿真波形
细节:
带流水线的加法器仿真波形放大
延时为456.289ns-456.067ns=0.222ns,相比没有流水线的毛刺时间区别并不大。且输入与输出不在同一个时钟周期,相隔较大。

输入改为8bit时:
8bit带流水线的无符号加法器
输出延迟略大,但可以在更高的时钟频率下运行。

 

两层流水线8bit加法器

高位与低位间加一级流水线。

module pipelined2_adder_8bit_input(
	IN1	,
	IN2	,
	CLK	,
	OUT	);
input	[7:0]	IN1, IN2;
output	[8:0]	OUT;
input	CLK;

reg		[7:0]	in1_d1R, in2_d1R;
reg		[3:0]	in1_high4,in2_high4,low4out; 
reg		[8:0]	OUT;
reg 	low4_carry;

always @ (posedge CLK) begin // input diff
	in1_d1R	<= IN1;
	in2_d1R	<= IN2;
end

always @ (posedge CLK) begin // second assembly line
	{low4_carry,low4out} <= in1_d1R[3:0] + in2_d1R[3:0];
	in1_high4 <= in1_d1R[7:4];
	in2_high4 <= in2_d1R[7:4];
end

always @ (posedge CLK) begin // combinational logic
	OUT[8:4] = in1_high4 + in2_high4 + low4_carry;
	OUT[3:0] = low4out;
end

endmodule

RTL Viewer:
Verilog RTL基础模块代码设计学习笔记_第17张图片
除了输入输出两层流水线外,在高位和低位加法之间又加了一层流水线。

仿真波形:
两层流水线8bit无符号加法器仿真波形
因为多一层流水线导致一开始延时更大,输入到对应输出延时更长,用了更多的寄存器。
但流水线让加法器可以运行在更高的时钟频率下,提高了大量计算时的效率。
所以用不用流水线需要从资源和效率两方面进行权衡。

流水线的更多介绍请参考这里。
摘录:

第三 使用流水线的优缺点
1) 优点: 流水线缩短了在一个时钟周期内给的那个信号必须通过的通路长度,增加了数据吞吐量,从而可以提高时钟
频率,但也导致了数据的延时。举例如下:
例如:一个 2 级组合逻辑,假定每级延迟相同为 Tpd,
1.无流水线的总延迟就是 2Tpd,可以在一个时钟周期完成,但是时钟周期受限制在 2Tpd;
2.流水线:
每一级加入寄存器(延迟为 Tco)后,单级的延迟为 Tpd+Tco,每级消耗一个时钟周期,流水线需要 2 个时钟周期来获得第一个计算结果,称 为首次延迟,它要 2*( Tpd+Tco),但是执行重复操作时,只要一个时钟周期来获得最后的计算结果,称为吞吐延迟( Tpd+Tco)。可见只要 Tco 小于 Tpd,流水线就可以提高速度。 特别需要说明的是,流水线并不减小单次操作的时间,减小的是整个数据的操作时间,请大家认真体会。
2) 缺点: 功耗增加,面积增加,硬件复杂度增加,特别对于复杂逻辑如 cpu 的流水线而言,流水越深,发生需要 hold 流水线或 reset 流水线的情况时,时间损失越大。 所以使用流水线并非有利无害,大家需权衡考虑。
————————————————
版权声明:本文为CSDN博主「Times_poem」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/times_poem/article/details/52033535

 

乘法器

电路描述

先要强调的是,乘法器是一种奢侈品会消耗大量的组合电路逻辑资源,一定要慎重使用。尤其是对于芯片内部没有硬件乘法器的FPGA芯片,需要更加慎重。 为什么会消耗如此多的资源呢,请回想一下我们在草稿纸上手工计算多位数乘法的过程,我们需要做很多次的加法,用电路计算乘法也是类似的。

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

module complement_multiplier(
	IN1	,
	IN2	,
	OUT	);
input signed	[3:0]	IN1, IN2;
output signed	[7:0]	OUT;

reg signed		[7:0]	OUT;

always @ (IN1 or IN2) begin
	OUT = IN1 * IN2;
end

endmodule 

 

乘法器的特点

仿真波形:
Verilog RTL基础模块代码设计学习笔记_第18张图片
补码乘法器的计算结果会扩展出1比特额外的符号位,从波形图中可以看到,最高2个比特几乎总是一样的。

乘法优化可以从乘法的计算方法上入手:
移位相加: A ∗ 3 = A ∗ 2 + A = ( A < < 1 ) + A A ∗ 15 = ( A < < 3 ) + ( A < < 2 ) + ( A < < 1 ) + A A*3=A*2+A=(A<<1)+A\\A*15=(A<<3)+(A<<2)+(A<<1)+A A3=A2+A=(A<<1)+AA15=(A<<3)+(A<<2)+(A<<1)+A
有时候数字电路在一个周期内并不能够完成多个变量同时相加的操作,而对于第二个式子,右侧有四位相加,可能会导致时序不满足计算要求。可以通过流水线来解决。

有些FPGA芯片内部有固化的硬件乘法器,对于这样的芯片,代码中的乘号 * 会变成芯片中的乘法器。
对于内部没有乘法器的FPGA芯片,代码中的乘号就会用逻辑单元拼接成加法器,再用加法器拼接成乘法器。

组合逻辑资源消耗量大:
可以通过选取没有硬件乘法器的FPGA芯片(本例选取Cyclone EP1C6F256C6),对比8bit的乘法器和加法器两者编译之后的资源开销来说明。
乘法器编译报告:
Verilog RTL基础模块代码设计学习笔记_第19张图片
加法器编译报告:
Verilog RTL基础模块代码设计学习笔记_第20张图片
加法器中逻辑单元仅用了9个,而乘法器中逻辑单元用了多达106个,可见乘法器内部组合逻辑之复杂。

 

时序逻辑电路

 

计数器

电路描述

计数器是最为常用的时序电路之一,计数器在数字电路里面的作用,就如C程序中的for循环一样。

最简单的计数器只有一个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的嵌套,参考上文中的优先编码器例子)。这正是利用代码来描述电路的优势所在。

RTL Viewer:
Verilog RTL基础模块代码设计学习笔记_第21张图片

 

最简单的模4计数器

只有CLK和OV

1
module counter4(
	CLK	,
	OV	);// Simple counter
	
input 	CLK	;
output	OV	;

reg		[1:0]	Q, D;
reg 	OV	;

parameter CNT_MAX_VAL = 3;

always @ (Q) begin			// counter	combinational logic
	if(Q < CNT_MAX_VAL)begin
		D = Q + 1'b1;
	end
	else  begin				// mayb it's sounds stupid , but u cant change one reg in which always is sentitive to it , which i just done
		D = 0;
	end
end

always @ (posedge CLK) begin// diff		temporal logic
	Q <= D;
end

always @ (Q) begin			// ov		combinational logic
	if(Q == CNT_MAX_VAL)
		OV = 1;
	else
		OV = 0;
end

endmodule

2
module counter4(
	CLK	,
	OV	);
	
input 	CLK	;
output	OV	;

reg		[1:0]	Q;
reg 	OV	;

parameter CNT_MAX_VAL = 3;

always @ (posedge CLK) begin// diff and combinational
	if(Q < CNT_MAX_VAL)begin
		Q <= Q + 1'b1;
	end
	else  begin
		Q <= 0;
	end
end

always @ (Q) begin			// ov		combinational logic
	if(Q == CNT_MAX_VAL)
		OV = 1;
	else
		OV = 0;
end

endmodule

在第二种写法里我把diff和逻辑电路写在一起了,因为我以前就是这么写的。但我现在觉得第一种分开写的更好一些。分开写可以让计数的组合逻辑部分更清晰,也可以把控制的部分放进组合逻辑电路,更符合实际电路的划分。

仿真波形:
无控制信号模4计数器仿真波形
输出有diff的延迟,约10ns。

 

含有多种控制信号的计数器

根据电路要求给不同的控制信号设置优先级。本例中同步清零CLR的优先级最高,使能EN次之,LOAD最低。
优先级高的写在if嵌套的外层。嵌套向内优先级依次降低。

module counter10(RST , CLK , EN , LOAD , DATA , CLR , OV , Q);

input	RST , CLK , EN , LOAD , CLR ;
input	[3:0]	DATA;
output	OV;
output	[3:0]	Q;// also cntval

reg		[3:0]	Q , D;
reg		OV;

parameter CNT_MAX_VAL = 9;

always @ (CLR or EN or LOAD or DATA or Q) begin	// control and counter	combinational logic
	if (CLR) begin		// CLR == 1 then D =0
		D = 0;
	end
	else begin			// else EN and LOAD
		if (EN) begin
			if (LOAD) begin
				D = DATA;
			end
			else begin						// then simple counter
				if (Q < CNT_MAX_VAL) begin	// Q dont reach MAXVAL then count
					D = Q + 1'b1;
				end
				else begin					// Q reach MAXVAL then D = 0
					D = 0;
				end
			end
		end
		else begin		// EN == 0 then D unchange
			D = Q;
		end
	end
end

always @ (posedge CLK or posedge RST) begin	// diff		temporal logic
	if(RST)
		Q <= 0;
	else
		Q <= D;
end

always @ (Q) begin		// ov	combinational logic
	if(Q == CNT_MAX_VAL)
		OV = 1;
	else
		OV = 0;
end


endmodule

仿真波形:
Verilog RTL基础模块代码设计学习笔记_第22张图片
观察120ns~200ns的波形:LOAD为1时将DATA的值传给Q,且传送值随DATA变化而变化。
观察280ns~320ns的波形:EN为0,所以计数停止了。而EN屏蔽了LOAD信号,所以DATA的值也没有传给Q。
观察440ns~480ns的波形:CLR同步置零, 在下一时钟上升沿将Q置为0,延迟约为7ns。CLR屏蔽了EN和LOAD信号,所以置零过程不受EN和LOAD的影响。
观察600ns~640ns的波形:因为LOAD控制信号为低电平,所以即使DATA有输入也不会传给Q。
观察740ns~780ns的波形:RST为异步置零,检测到RST为高电平时即开始置零过程,延迟约9ns。
最后看CLK、Q和OV,可以正常计数。
注意仿真时最好在最前面RST一下,把内部的寄存器清零。

 

状态机

电路描述

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

关键:把要解决的问题映射到状态空间,包括:

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

 

三段式状态机

比如现在我们要实现一个可乐售卖机的状态机电路,假设可乐3分钱1罐,机器只接受1分钱的硬币,当投入第三个硬币的时候,投出一罐可乐
该电路的状态转移逻辑和电路RTL逻辑如下图所示:
Verilog RTL基础模块代码设计学习笔记_第23张图片

从电路的RTL结构上来看,状态机和计数器的RTL是比较相似的,但是它们的应用含义不同。
具体的状态跳转和输出逻辑如下面的表格定义。当设计状态机时,建议先把这两个表格画出来再开始写代码

状态跳转逻辑表:

当前状态 输入 次态
ST_0_CENT CENT1IN==0 ST_0_CENT
ST_0_CENT CENT1IN==1 ST_1_CENT
ST_1_CENT CENT1IN==0 ST_1_CENT
ST_1_CENT CENT1IN==1 ST_2_CENT
ST_2_CENT CENT1IN==0 ST_2_CENT
ST_2_CENT CENT1IN==1 ST_3_CENT
ST_3_CENT Dont care ST_0_CENT

输出逻辑表:

当前状态 输出 TINOUT
ST_0_CENT 0
ST_1_CENT 0
ST_2_CENT 0
ST_3_CENT 1
module state_machine_3(
  CLK       ,   // clock
  RST       ,   // reset
  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 :begin if(CENT1IN) next_state = ST_1_CENT ; else next_state = ST_0_CENT; end
    ST_1_CENT :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

// calc output
always @ (stateR) begin
  if(stateR == ST_3_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

此处的代码有一个always专门控制输出,故为moore型。

对于代码编写的规范的状态机,EDA工具可以检测出来,编译之后,可以在Tools-Netlist Viewers-State Machine Viewer 里面看到你写的状态机的状态转移图和表达式:
Verilog RTL基础模块代码设计学习笔记_第24张图片

 

序列信号检测器

状态机常用于设计序列信号检测器。
比如要设计一个检测’1011’的检测器。
先画状态转移图:
Verilog RTL基础模块代码设计学习笔记_第25张图片
状态跳转逻辑和输出逻辑在图上都已经很明了了,熟练的话可以不列表。

module detector_1011_moore(CLK , RST , EN , CENT1IN , TINOUT);

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

parameter ST_0_CENT = 0;	//suggest that define states as parameter
parameter ST_1_CENT = 1;
parameter ST_2_CENT = 2;
parameter ST_3_CENT = 3;
parameter ST_4_CENT = 4;

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

always @ (CENT1IN or EN or stateR) begin	// next state
	if (EN == 1) begin
		case(stateR)
			ST_0_CENT :	begin if(CENT1IN) next_state = ST_1_CENT ; else next_state = ST_0_CENT ; end
			ST_1_CENT :	begin if(CENT1IN) next_state = ST_1_CENT ; else next_state = ST_2_CENT ; end
			ST_2_CENT :	begin if(CENT1IN) next_state = ST_3_CENT ; else next_state = ST_0_CENT ; end
			ST_3_CENT :	begin if(CENT1IN) next_state = ST_4_CENT ; else next_state = ST_2_CENT ; end
			ST_4_CENT :	begin if(CENT1IN) next_state = ST_0_CENT ; else next_state = ST_0_CENT ; end
		endcase
	end
	else begin
		next_state = stateR;
	end
end

always @ (stateR) begin						// tinout
	if(stateR == ST_4_CENT)
		TINOUT = 1;
	else
		TINOUT = 0;
end

always @ (posedge CLK or posedge RST) begin	// diff
	if (RST)
		stateR <= 0;
	else
		stateR <= next_state;
end

endmodule

仿真波形:
Verilog RTL基础模块代码设计学习笔记_第26张图片
如图,输入为1010110101100111…,其中第一个1011检测输出正确。
第二个1011时因为0被EN屏蔽了,所以没有输出。而10011中第二个0被EN屏蔽了,所以输出正常。这两处说明EN使能工作符合要求。

 

移位寄存器

电路描述

串行数据和并行数据之间的相互转换是在接口设计中很常见的功能。
一般而言,数据在FPGA内部都是并行传递的,当通过串行接口协议(例如SPI,I2C,I2S等)把数据从FPGA内部传送到一个外部芯片(例如一片EEPROM存储器或是一片音频DAC)时就需要用到串并转换了。其核心的电路是移位寄存器。
常用的串并转换电路有“串行-并行”和“并行-串行”两种,其RTL电路如下:
Verilog RTL基础模块代码设计学习笔记_第27张图片

 

串入并出移位寄存器

module SIPO_shift_register(
  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];

always @ (posedge CLK or posedge RST) begin	// temporal logic
  if(RST) 
    shift_R[3:0] <= 0;
  else
    if(EN) begin							// EN = 1 , begin inputting
      shift_R[3:1] <= shift_R[2:0];
      shift_R[0]   <= IN;
    end
    else begin
      shift_R[3:0] <= shift_R[3:0];			// EN = 0 , shift_R unchange
    end
end

endmodule

仿真波形:
Verilog RTL基础模块代码设计学习笔记_第28张图片

 

并入串出移位寄存器

module PISO_shift_register(IN , RST , LOAD , EN , CLK , OUT);

input	[3:0]	IN;
input	RST , LOAD , EN , CLK;
output	OUT;

reg		[3:0]	shift_R;

assign OUT = shift_R[3];

always @ (posedge CLK or posedge RST) begin		// temporal logic
	if (RST)
		shift_R[3:0] <= 0;
	else
		if (EN) begin							// EN = 1 , begin inputting
			if (LOAD) begin						// LOAD = 1 , reload input
				shift_R[3:0] <= IN[3:0];
			end
			else begin							// LOAD = 0 , serial output
				shift_R[3:1] <= shift_R[2:0];
				shift_R[0] <= 0;
			end
		end
		else begin								// EN = 0 , shift_R unchange
			shift_R[3:0] <= shift_R[3:0];
		end
end

endmodule

一开始写的时候乍一看RTL感觉很复杂,但其实理清楚逻辑就清晰了。
先看控制信号的优先级。这里的优先级RST异步置零最高,RST写在敏感信号表内。在模块内部是EN使能信号优先级仅次RST,因为EN为0的话不会改变寄存器的内容,所以先套上EN的判断。然后就是LOAD了。当LOAD为1时重新采样输入,当LOAD为0时进行移位寄存。
这样三个if的嵌套就很明了了,写起来也就很容易了。

仿真波形:
并入串出移位寄存器仿真波形
如图,输入有四位,所以四个clk时钟重新采样一次输入。
输出为1010 0010 0000 1011 1001 0100 0001 ,与输入相符。

你可能感兴趣的:(verilog,fpga)