Verilog学习笔记(5):Verilog高级程序设计

这篇具有很好参考价值的文章主要介绍了Verilog学习笔记(5):Verilog高级程序设计。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


1.数字电路系统设计的层次化

Verilog学习笔记(5):Verilog高级程序设计
串行加法器:
Verilog学习笔记(5):Verilog高级程序设计
一个四位串行加法器由4个全加器构成。全加器是串行加法器的子模块,而全加器是由基本的逻辑门构成,这些基本的逻辑门就是所说的叶子模块。这个设计中运用叶子模块(基本逻辑门)搭建成子模块(全加器),再用子模块搭建成所需要的电路(串行加法器)。
显然,Bottom-Up的设计方法没有明显的规律可循,主要依靠设计者的实践经验和熟练的设计技巧,用逐步试探的方法最后设计出一个完整的数字系统。系统的各项性能指标只有在系统构成后才能分析测试 此种设计方法常用于原理图的设计中,相比于其它方法此种方法对于实现各个子模块电路所需的时间较短。
Verilog学习笔记(5):Verilog高级程序设计
使用Top-Down设计方法对一个典型cpu进行设计:
Verilog学习笔记(5):Verilog高级程序设计

向量点积乘法器:
采用模块层次化设计方法,设计4维向量点积乘法器,其中向量a =(a1,a2,a3,a4);b=(b1,b2,b3,b4)。点积乘法规则:
Verilog学习笔记(5):Verilog高级程序设计
Verilog代码:

module vector(a1,a2,a3,a4,b1,b2,b3,b4,out); 
 input [3:0] a1,a2,a3,a4,b1,b2,b3,b4;
 output [9:0] out;
 wire [7:0] out1,out2,out3,out4; 
 wire [8:0] out5, out6; 
 wire [9:0] out;
 mul_addtree U1(.x(a1), .y(b1), .out(out1)); 
 mul_addtree U2(.x(a2), .y(b2), .out(out2));
 mul_addtree U3(.x(a3), .y(b3), .out(out3));
 mul_addtree U4(.x(a4), .y(b4), .out(out4)); 
 add #(8) U5(a(out1), .b(out2), .out(out5)); 
 add #(8) U6(.a(out3), .b(out4), .out(out6)); 
 add #(9) U7(.a(out5), .b(out6), .out(out));
endmodule
//adder 
module add(a,b,out); 
 parameter size=8;
 input [size-1:0] a,b; 
 output [size:0j out; 
 assign out=a+b;
endmodule
//Multiplier
module mul_addtree(mul_a,mul_b,mul_out); 
 input [3:0] mul_a.mul_b; 
 output [7:0] mul_out;
 wire [3:0] muI_out; 
 wire [3:0] stored0,stored1,stored2,stored3;
 wire [3:0] add0l, add23;
	assign stored3=mul_b[3]?{1'b0,mul_a,3'b0):8’b0; 
	assign stored2=mul_b[2]?{2’b0,mul_a ,2’b0}:8’b0; 
	assign stored1=mul_b[1]?{3'b0,mul_a,1'b0}:8’b0; 
	assign storedo=mul_b[0]?{4’b0,mul_a}:8’b0; 
	assign add01=storedl +stored0; 
	assign add23=stored3+stored2; 
	assign mul_out=add0l +add23; 
endmodule

2.典型电路设计

2.1加法器树乘法器

加法器树乘法器的设计思想是”移位后加",并且加法运算采用加法器树的形式。乘法运算的过程是:被乘数与乘数的每一位相乘并且乘以相应的权值,最后将所得的结果相加,便得到了最终的乘法结果。
例:下图是一个4位的乘法器结构,用verilog设计一个加法器树4位乘法器
Verilog学习笔记(5):Verilog高级程序设计

module mul_addtree(mul_a,mul_b,mul_out);
 input [3:0] mul_a,mul_b;
 output [7:0] mul_out;
 wire [7:0] mul_out;
 wire [7:0] stored0,stored1,stored2,stored3;
 wire [7:0] add01,add23;
 assign stored3 = mul_b[3]?{1'b0,mul_a,3'b0}:8'b0;
 assign stored2 = mul_b[2]?{2'b0,mul_a,2'b0}:8'b0;
 assign stored1 = mul_b[1]?{3'b0,mul_a,1'b0}:8'b0;
 assign stored0 = mul_b[0]?{4'b0,mul_a,0'b0}:8'b0;
 assign add01=stored1+stored0;
 assign add23=stored3+stored2;
 assign mul_out=add01 + add23;
endmodule
module mult_addtree_tb;
 reg [3:0] mult_a;
 reg [3:0] mult_b;
 wire [7:0] mult_out;
 mul_addtree U1(.mul_a(mult_a), .mul_b(mult_b), .mul_out(mult_out));
 initial
 	begin
 		mult_a = a;
 		mult_b = 0;
 		repeat(9)
 			begin
 				#20 mult_a = mult_a + 1;
 				mult_b = mult_b + 1;
 			end
 	end
 endmodule

Verilog学习笔记(5):Verilog高级程序设计

流水线结构:
例:下图是一个4位的乘法器结构, 用Verilog设计一个两级流水线加法器树4位乘法器。

Verilog学习笔记(5):Verilog高级程序设计
两级流水线加法器树4位乘法器结构如图所示,通过在第一级与第二级、第二级与第三级加法器之间插入D触发器组,可以实现两级流水线设计。

module mul_addtree_2_stage(clk,clr,mul_a,mul_b,mul_out); 
 input clk,clr;
 input [3:0] mul_a,mul_b; 
 output [7:0] mul_out;
 reg [7:0] add_tmp_1,add_tmp_2,mul_out;
 wire [7:0] stored0,stored1,stored2,stored3;
assign stored3 = mul_b[3]?{1'b0,mul_a,3'b0}:8'b0; 
assign stored2 = mul_b[2]?{2'b0,mul_a,2'b0}:8'b0;
assign stored1 = mul_b[1]?{3'b0,mul_a,1'b0}:8'b0; 
assign stored0 = mul_b[0]?{4'b0,mul_a}:8'b0; 
always@(posedge clk or negedge clr) 
 begin
	if(!clr) 
		begin 
			add_tmp_1 <= 8'b0000_0000; 
			add_tmp_2 <= 8'b0000_0000; 
			mul_out <= 8'b0000_0000;
		end
	else 
		begin 
			add_tmp_1 <= stored3 + stored2;
			add_tmp_2 <= stored1 +stored0; 	
			mul_out <= add_tmp_1 + add_tmp_2;
		end
 end
endmodule
module mult_addtree_2_stag_tb;
 reg clk,clr;
 reg [3:0] mult_a,mult_b;
 wire [7:0] mult_out;
 mul_addtree_2_tage U1(.mul_a(mult_a), .mul_b(mult_b), .mul_out(mult_out), .clk(clk), .clr(clr));
 initial
 	begin
 		clk = 0;clr = 0;mult_a = 1;mult_b = 1;
 		#5 clr = 1;
 	end
 always #10 clk = ~clk;
 initial
 	begin
 		repeat(5)
 			begin
 				#20 mult_a = mult_a + 1;
 				mult_b = mult_b + 1;
 			end
 	end
 endmodule

Verilog学习笔记(5):Verilog高级程序设计

2.2Wallace树乘法器

Wallace树乘法器运算原理如下图,其中FA为全加器HA为半加器。其基本原理是,加法从数据最密集的地方开始,不断地反复使用全加器半加器来覆盖"树"。这一级全加器是一个3输入2输出的器件,因此全加器又称为3-2压缩器。通过全加器将树的深度不断缩减,最终缩减为一个深度为2的树。最后一级则采用一个简单的两输入加法器组成。
Verilog学习笔记(5):Verilog高级程序设计
Verilog学习笔记(5):Verilog高级程序设计

module wallace(x,y,out);
	parameter size=4;
	input [size-1:0] x,y;
	output [2*size-1:0] out;
	wire [size*size-1:0] a; 
	wire [1:0] b0,b1,c0,c1,c2,c3; 
	wire [5:0] add_a,add_b; 
	wire [6:0] add_out;
	wire [2*size-1 :0] out;
	assign a={x[3],x[3],x[2],x[2],x[1],x[3],x[1],x[0],x[3],x[2],x[1],x[0],x[2],x[1],x[0],x[0]}
			& 
			 {y[3],y[2],y[3],y[2],y[3],y[1],y[2],y[3],y[0],y[1],y[1],y[2],y[0],y[0],y[1],y[0]};
			 
  hadd U1(.x(a[8]), .y(a[9]), .out(b0));
  hadd U2(.x(a[11]), .y(a(a[12]), .out(b1));
  hadd U3(.x(a[4]), .y(a[5]), .out(c0));
 
  fadd U4(.x(a[6]), .y(a[7]), .z(b0[0]),.out(c1)); 
  fadd U5(.x(a[13]), .y(a[14]), . z(b0[1]), .out(c2));
  fadd U6(.x(b1[0]), .y(a[10]), .z(b1[1], .out(c3));
 
 assign add_a = {c3[1],c2[1],c1[1],c0[1],a[3],a[1]); 
 assign add_b ={ a[15],c3[0],c2[0],c1[0],c0[0],a[2]};
 assign add_out = add_a + add_b;
 assign out={add_out,a[0]};

endmodule
module fadd(x, y, z, out); 
	output [1:0] out;
	input x,y,z;
	assign out=x+y+z; 
endmodule
module hadd(x, y, out);
	output [1:0] out;
	input x.y;
	assign out=x+y;
endmodule 
module wallace_tb; 
 reg [3:0] x, y;
 wire [7:0] out;
 wallace m(.x(x), .y(y), .out(out)); 
initial
	begin
		x=3; y=4;
		#20 x=2; y=3;
		#20 x=6; y=8; 
	end
endmodule

Verilog学习笔记(5):Verilog高级程序设计

2.3复数乘法器

Verilog学习笔记(5):Verilog高级程序设计
复数乘法器的电路结构如下图所示。将复数x的实部与复数y的实部相乘,减去x的虚部与y的虚部相乘,得到输出结果的实部。将x的实部与y的虚部相乘,加上x的虚部与y的实部相乘,得到输出结果的虚部。

Verilog学习笔记(5):Verilog高级程序设计

module complex(a,b,c,d,out_real,out_im);
 input [3:0]a,b,c,d;
 output [8:0] out_real,out_im; 
 wire [7:0] sub1,sub2,add1,add2; 
 wallace U1(.x(a), .y(c), .out(sub1)); 
 wallace U2(.x(b), .y(d), .out(sub2)); 
 wallace U3(.x(a), .y(d), .out(add1)); 
 wallace U4(.x(b), .y(c), .out(add2));
 assign out_real=subl - sub2; 
 assign out_im = add1 + add2;
endmodule
 module complex_tb;
 	reg [3:0] a,b,c,d;
 	wire [8:0] out_real;
 	wire [8:0] out_im;
 	complex U1(.a(a), .b(b), .c(c), .d(d), .out_real(out_real), .out_im(out_im));
 	initial
 		begin
 			a=2;b=2;c=5;d=4;
 			#10
 			a=4;b=3;c=2;d=1;
 			#10
 			a=3;b=2;c=3;d=4
 		end
 endmodule

Verilog学习笔记(5):Verilog高级程序设计

2.4 FIR滤波器设计

有限冲激响应(FIR)滤波器就是一种常用的数字滤波器,采用对己输入样值的加权和来形成它的输出。其系统函数为:
Verilog学习笔记(5):Verilog高级程序设计
其中Z-1表示延时一个时钟周期,Z-2表示延时两个时钟周期。
对于输入序列X[n]的FIR滤波器可用下图所示的结构示意图来表示,其中X[n]是输入数据流。各级的输入连接和输出连接被称为抽头,并且系数(b0,b1,…,bn)被称为抽头系数。一个M阶的FIR滤波器将会有M+1个抽头。
通过移位寄存器用每个时钟边沿n(时间下标)处的数据流采样值乘以抽头系数,并将它们加起来形成输出Y[n]。
Verilog学习笔记(5):Verilog高级程序设计
代码如下:

module FIR(Data_out, Data_in ,dock,reset); //模块FIR
 output [9:0] Data_out;
 input [3:0] Data_in;
 input clock,reset;
 wire [9:0] Data_out;
 wire [3:0] samples_0,sampies_1 ,samples_2,samples_3,samples_4,
  			samples_5, samples_6, samples_7, samples_8;
 shift_register U1(.Data_in(Data_in), .clock(ciock), .reset(reset),
				   .samples_0(samples_0), .samples_1(samples_1),
				   .samples_2(samples_2), .samples_3(samples_3),
				   .samples_4(samples_4), .samples_5(samples_5),
				   .samples_6(sam ples_6), .samples_7(samples_7),
				   .samples_8(samples_8));
 caculator U2(.samples_0(samples_0), .samples_1 (samples_1),
			  .samples_2(samples_2), .sam ples_3(samples_3),
			  .samples_4(samples_4), .samples_5(sam ples_5),
			  .sampIes_6(samples_6), .samples_7(sampies_7),
			  .samples_8(sam pies_B), .Data_out( Data_out)); 
endmodule

shift_register
module shift_register(Data_in,clock,reset,samples_0,samples_1 ,samples_2,samples_3,
					  samples_4,samples_5,samples_6,samples_7, samples_8); 
 input [3:0] Data_in;
 input clock,reset;
 output [3:0] samples_0,samples_1 ,samples_2,samples_3,samples_4, 
 			  samples_5,samples_6, samples_7,samples_8;
 reg [3:0] samples_0,samples_l ,samples_2,samples_3,samples_4, 
 		   samples_5, sam pies 6, sampies_7,samptes_8;
always(posedge clock or negedge reset)
	begin
		if(reset)
			begin
				samples_0 <= 4’b0; 
				samples_1 <= 4’b0; 
				samples_2 <= 4’b0; 
				samples_3 <= 4’b0; 
				samples_4 <= 4’b0; 
				samples_5 <= 4’b0; 
				samples_6 <= 4’b0; 
				samples_7 <= 4’b0; 
				samples_8 <= 4’b0;
			end
		else
			begin
				samples_0 <= Data_in;
				samples_1 <= samples_0; 
				samples_2 <= samples_1; 
				samples_3 <= samples_2; 
				samples_4 <= samples_3; 
				samples_5 <= samples_4; 
				samples_6 <= samples_5; 
				samples_7 <= samples_6; 
				samples_8 <= samples_7; 
			end
	end
endmodule

//模块caculator
module caculator(sampies_O,samples_i ,samples_2,samples_3,samples_4,
				 samples_5,samples_6, samples_7,samples_8,Data_out);
 input [3:0] samples_0,samples_1 ,samples_2,samples_3,samples_4,samples_5,samples_6, samples_7,samples_8;
 output [9:0] Data_out; 
 wire [9:0] Data_out; 
 wire [3:0] out_tmp_1 ,out_tmp_2,out_tmp_3,out_tmp_4,out_tmp_5; 
 wire [7:0] outl,out2,out3,out4,out5;
 parameter b0=4’b0010; 
 parameter b1=4’b0011; 
 parameter b2=4’b0110; 
 parameter b3=4’b1010; 
 parameter b4=4’b1100; 
 mul_addtree U1(.mul_a(b0),.mul_b(out_tmp_1),.mul_out(out1)); 
 mul_addtree U2(.mul_a(b1),.mul_b(out_tmp_2),.mul_out(out2)); 
 mul_addtree U3(.mul_a(b2),.mul_b(out_tmp_3),.mul_out(out3)); 
 mul_addtree U4(.mul_a(b3),.mul_b(out_tmp_4),.mul_out(out4)); 
 mul_addtree U5(.mul_a(b4),.mul_b(samples_4),.mul_out(out5)); 
 assign out_tmp_1 = samples_0 + samples_8;
 assign out_tmp_2  =samples_1 + samples7; 
 assign out_tmp_3 = samples_2 + samples_6; 
 assign out_tmp_4 = samples3 + samples_5; 
 assign Data_out = out1 +out2 + out3 + out4  + qout5; 
endmodule
//模块FIR_tb
module FIR_tb; 
 reg clock,reset; 
  reg [3:0] Data_in; 
  wire [9:0] Data_out;
 FIR U1(.Data_out(Data_out), .Data_in(Data_in), .clock(clock), reset(reset)); 
 initial
	begin
		Data_in = 0; clock = 0; reset = 1;
		#10 reset = 0; 
	end
	always
		begin
			#5 clock <= ~clock;
			#5 Data_in <= Data_in+1;
		end
endmodule

Verilog学习笔记(5):Verilog高级程序设计

2.5 片内存储器的设计

(1)RAM 的 Verilog描述
RAM是随机存储器,存储单元的内容可按需随意取出或存入。这种存储器在断电后将丢失掉所有数据,一般用来存储一些短时间内使用的程序和数据。其内部结构如下所示:
Verilog学习笔记(5):Verilog高级程序设计
例:用Verilog设计深度为8,位宽为8的单端口RAM。单口RAM,只有一套地址总线,读操作和写操作是分开的。

module ram_single(clk, addm, cs_n, we_n, din, dout);
input clk;	 //clock signal
input [2:0] addm;	 //address signal
input cs_n;	 //chip select signal
input we_n; 	//write enable signal
input [7:0] din; 	//input data
output[7:O] dout; 	//output data
reg [7:0] dout;
reg [7:0] raml [7:0]; 	//8*8 bites register 
aIways(posedge clk)
	begin
		if(cs_n)
			dout <= 8’bzzzz_zzzz;
		else 
			if(we_n) 	//read data 
				dout <= raml[addm];
			else	 //write data
				raml[addm] <= din;
	end
end module

module ram single tb;
 reg clk, we_n, cs_n;
 reg [2:0] addm;
 reg [7:0] din;
 wire [7:0] dout;
 ram_single U1(.clk(clk),.addm(addm),.cs_n(cs_n),.we_n(we_n),.din(din),.dout(dout));
 initial 
 	begin
 		clk=0; addm=0; cs_n=1; we_n=0; din=0;
 		#5 cs_n=0;
 		#315 we_n=1;
	end
always #10 clk=~clk;
initial
	begin
		repeat(7) 
			begin
				#40 addm=addm+1;
				din=din+1;
			end
			#40 repeat(7)
			#40 addm=addm-1;
	end
endmodule

Verilog学习笔记(5):Verilog高级程序设计
例:用Verilog设计深度为8,位宽为8的双端口RAM。双口RAM具有两套地址总线,一套用于读数据,另一套用于写数据。 二者可以分别独立操作。

module ram_dual(q, addr _n, addr_out, d, we, rd, clk1, clk2);
 output [7:0] q;	//output data
 input [7:0] d;	//input data
 input [2:0] addr_in; 	//write data address signal
 input [2:0] addr_out; 	//output data address signal
 input we;	//write data control signal
 input rd;	//read data control signal
 input clk1; 	//write data clock
 input clk2; 	//read data clock
 reg[7:0] q;
 reg[7:0] mem[7:0];	 //8*8 bites register
 always@(posedge clk1)
	begin
		if(we)
			mem[addr_n] <= d;
	end
 always@(posedge clk2)
	begin
		if(rd)
			q <= mem[addr_out];
	end
endmodule

module ram_dual_tb;
 reg clk1, clk2, we, rd;
 reg [2:0] addr_in;
 reg [2:0 ]addr_out;
 reg [7:0] d;
 wire [7:0] q;
 ram_dual U1(.q(q),.addr_in(addr_in),.addr_out(addr_out),.d(d),.we(we),.rd(rd),.clk1(clk1),.clk2(clk2));
 initial
 	begin
 		clk1=0; clk2=0; we=1; rd=0; addr_in=0; addr_out=0; d=0;
 		#320 we=0;
 		rd=1;
	end
 always
 	begin
 		#10 clk1 = ~clk1;
 		clk2 = ~clk2;
 	end
initial
	begin
		repeat(7)
			begin
				#40 addr_in=addr_in+1;
				d=d+1;
			end
			#40
			repeat(7) #40 addr_out=addr_out+1;
	end
endmodule

Verilog学习笔记(5):Verilog高级程序设计
(2)ROM的Verilog描述
ROM即只读存储器,是一种只能读出事先存储的数据的存储器,其特性是存入数据无法改变,也就是说这种存储器只能读不能写。由于ROM在断电之后数据不会丢失,所以通常用在不需经常变更资料的电子或电脑系统中,资料并不会因为电源关闭而消失。

module rom(dout, clk, addm, cs_n);
 input clk, cs_n;
 input [2:0] addm;
 output [7:0] dout;
 reg [7:0] dout;
 reg [7:0] rom[7:0];
 initial
 	begin
		rom[0]=8b0000_0000;
		rom[1]=8b0000_0001;
		rom[2]=8b0000_0010;
		rom[3]=8b0000_0011;
		rom[4]=8b0000_0100
		rom[5]=8b0000_0101;
		rom[6]=8b0000_0110;
		rom[7]=8'b0000_0111;
	end
 always@(posedge clk)
 	begin
		if(cs_n) 
			dout<=8'bzzzz_zzzz;
		else
			dout<=rom[addm];
	end
endmodule

module rom_tb;
 reg clk, cs_n;
 reg [2:0] addm;
 wire [7:0] dout;
 rom U1(.dout(dout),.clk(clk),.addm(addm),.cs_n(cs_n));
 initial 
 	begin
		clk=0; addm=0; cs_n=0;
	end
 always #10 clk=~clk;
 initial 
 	begin
		repeat(7)
		#20 addm=addm+1;
	end
endmodule

Verilog学习笔记(5):Verilog高级程序设计

2.6 FIFO设计

FIFO(First In First Out) 是一种先进先出的数据缓存器,通常用于接口电路的数据缓存。与普通存储器的区别是没有外部读写地址线,可以使用两个时钟分别进行写和读操作。FIFO只能顺序写入数据和顺序读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
FIFO由存储器块和对数据进出FIFO的通道进行管理的控制器构成,每次只对一个寄存器提供存取操作,而不是对整个寄存器阵列进行。FIFO有两个地址指针,一个用于将数据写入下一个可用的存储单元,一个用于读取下一个未读存储单元的操作。读写数据必须一次进行。
其读写过程如图所示:
Verilog学习笔记(5):Verilog高级程序设计
当一个堆栈为空时 (图A),读数据指针和写数据指针都指向第一个存储单元如所示;当写入一个数据时(图B)写数据指针将指向下个存储单元;经过七次写数据操作后(图C)写指针将指向最后一个数据单元;当经过连续八次写操作之后写指针将回到首单元并且显示堆栈状态为满(图D)。数据的读操作和写操作相似,当读出一个数据时,读数据指针将移向下一个存储单元,直到读出全部的数据,此时读指针回到首单元,堆栈状态显示为空。

一个FIFO的组成一般包括两个部分: 地址控制部分和存储数据的RAM部分。如下图所示。地址控制部分可以根据读写指令生成RAM地址。RAM用于存储堆栈数据,并根据控制部分生成的地址信号进行数据的存储和读取操作。这里的RAM采用的是前面提到的双口RAM。
Verilog学习笔记(5):Verilog高级程序设计
例: 用Verilog HDL设计深度为8,位宽为8的FIFO

//顶层模块:
module FIFO_buffer(clk,rst,write_to_stack,read_from_stack,Data_in,Data_out);
 input clk,rst;
 input write_to_stack,read_from_stack;
 input [7:0] Data_in;
 output [7:0] Data_out;
 wire [7:0] Data_out;
 wire stack_full, stack_empty;
 wire [2:0] addr_in, addr_out;
 FIFO_control U1(.stack_full(stack_full),.stack_empty(stack_empty),.write_to_stack(write_to_stack),
 				 .write_ptr(addr_in),read_ptr(addr_out),.read from stack(read from stack),
 				 .clk(clk),.rst(rst));
 ram_dual U2(.q(Data out),.addr_in(addr_in),.addr_out(addr_out),.d(Data_in),
  			 .we(write_to_stack),.rd(read_from_stack),.clk1(clk),.clk2(clk));
endmodule

//控制模块:
module FIFO_control(write_ptr, read_ptr, stack_full, stack_empty, write_to_stack,read_from_stack, clk, rst);
 parameter stack_width=8;
 parameter stack_height=8
 parameter stack_ptr_width=3,

 output stack_full;		//stack full flag
 output stack_empty;		//stack empty flag
 output [stack_ptr_width-1:0] read_ptr;  //read data address
 output[stack_ptr_width-1:0] write ptr;  //write data address

 input write_to_stack;   //write data to stack
 input read_from_stack;  //read data from stack

 input clk;
 input rst;

reg [stack_ptr_width-1:0] read_ptr;
reg [stack_ptr_width-1:0] write_ptr;
reg [stack_ptr_width:0] ptr_gap;
reg [stack_width-1:0] Data_out;
reg [stack_width-1:0] stack[stack_height-1:0];

 //stack status signal
 assign stack_full=(ptr_gap==stack height);
 assign stack_empty=(ptr_gap==0);
 
 always@(posedge clk or posedge rst)
 	begin
		if(rst)
			begin
				Data_out<=0;
				read_ptr<=0;
				write_ptr<=0;
				ptr_gap<=0;
			end
		else if(write_to_stack && (!stack_full) && (!read_from_stack))
			begin
				write_ptr<=write_ptr+1;
				ptr_gap<=ptr_gap+1;
			end
		else if(!write_to_stack && (!stack_empty) && (read_from_stack))
			begin
				read_ptr<=read_ptr+1;
				ptr_gap<=ptr_gap-1;
			end
		else if(write_to_stack && stack_empty && read_from_stack)
			begin
				write_ptr<=write_ptr+1;
				ptr_gap<=ptr_gap+1;
			end
		else if(write_to_stack && stack_full && read_from_stack)
			begin
				read_ptr<=read_ptr+1;
				ptr_gap<=ptr_gap-1;
			end
		else if(write_to_stack && read_from_stack&& (!stack_full)&&(!stack_empty))
			begin
				read_ptr<=read_ptr+1;
				write_ptr<=write_ptr+1;
			end
	end
endmodule

module FIFO_tb;
 reg clk, rst;
 reg [7:0] Data_in;
 reg write_to_stack, read_from_stack;
 wire [7:0] Data_out;
 FIFO_buffer U1(.clk(clk),.rst(rst),.write_to_stack(write_to_stack),
			    .read_from_stack(read_from_stack),.Data_in(Data_in),.Data_out(Data_out));
initial
	begin
		clk=0; rst=1; Data_in=0, write_to-stack=1; read_from_stack=0;
		#5 rst=0;
		#155 write_to_stack=0;
		read _rom_stack=1:
	end
always #10 clk = ~clk;
	initial
		begin
			repeat(7)
			#20 Data_in =Data_in+1;
		end
endmodule

Verilog学习笔记(5):Verilog高级程序设计

2.7 键盘扫描和编码器

键盘扫描和编码器用于在拥有键盘的数字系统中手工输入数据,通过检测按键是否按下,产生一个唯一对应此按键的扫描码。
例:用Verilog设计十六进制键盘电路的键盘扫描和编码器:
Verilog学习笔记(5):Verilog高级程序设计
控制信号状态机转移图如下图所示:
Verilog学习笔记(5):Verilog高级程序设计
Verilog学习笔记(5):Verilog高级程序设计
详情见:16进制键盘扫描器的Verilog实现

此时行列线的交叉处就是按键的位置。根据已确定的按键的位置输出其对应的编码信息。其键盘编码表如下表所示。

Key Row[3:0] Col[3:0] Code
0 0001 0001 0000
1 0001 0010 0001
2 0001 0100 0010
3 0001 1000 0011
4 0010 0001 0100
5 0010 0010 0101
6 0010 0100 0110
7 0010 1000 0111
8 0100 0001 1000
9 0100 0010 1001
A 0100 0100 1010
B 0100 1000 1011
C 1000 0001 1100
D 1000 0010 1101
E 1000 0100 1110
F 1000 1000 1111

为了使测试更接近于真实的物理环境,测试平台中必须包括模拟按键状态的信号发生器,能确认按键对应行线的模块Row_signal和被测试模块Hex Keypad Grayhill 072。模拟按键状态的信号发生器可以嵌入在测试平台中,通过不断地给key信号赋值,模拟产生不同的按键信号。Row_Signal模块用于检测按键的有效性并确定按键所处的行。而Synchronizer模块通过检测各个行线值的或来确定是否有按键按下,当此模块的输出发生变化时,被测模块Hex Keypad Grayhil 072将会确定按键的位置并输出相应的代码

其Verilog HDL程序代码是:

// 顶层模块:
module keypad(clock,reset,row,code,vaild,col);
 input clock,reset;
 input [3:0] row;
 output [3:0] code;
 output vaild;
 output [3:0] col;
 wire s_row;
 hex_keypad_grayhill U1(.code(code),.col(col),.valid(valid),
 						.row(row),.s_row(s_row),.clock(clock),.reset(reset));
 synchronizer U2(.s_row(srow),.row(row),.clock(clock),.reset(reset));
endmodule

//编码模块:
module hex_keypad_grayhill(code,col,valid,row,s_row,clock,reset);
 output [3:0] code;
 output valid;
 output [3:0] col;
 input [3:0] row;
 inputs row;
 input clock,reset;
 reg [3:0] col;
 reg[3:0] code;
 reg [5:0] state,next_state,
 parameter s_0=6'b000001,s_1=6'b000010,s_2=6'b000100;
 parameter s_3=6'b001000,s_4=6'b010000,s_5=6'b100000;
 assign valid=((state==s_1)|(state==s_2)|(state==s_3)|(state==s_4))&&row;
 always@(row or col)
 	case(frow,col})
 		8'b0001_0001: code=0;
 		8'b0001_0010: code=1;
 		8'b0001_0100: code=2;
 		8'b0001_1000: code=3;
		8'b0010_0001: code=4;
		8'b0010_0010: code=5;
		8'b0010_0100: code=6;
		8'b0010_1000: code=7;
		8'b0100_0001: code=8,
		8'b0100_0010: code=9;
		8'b0100_0100: code=10;
		8'b0100_1000: code=11;
		8'b1000_0001: code=12;
		8'b1000_0010: code=13;
		8'b1000_0100: code=14;
		8'b1000_1000: code=15;
		default code=0;
	endcase
 always@(state or s_row or row) 	//next-state logic
begin
	col=0:next_state=state;
	case(state)
		s_0:
			begin
				col=15;
				if(s_row) next_state=s_1;
			end
		s_1:
			begin
				col=1;
					if(row) next_state=s_5;
					else next_state=s_2
			end
		s_2:
			begin
				col=2;
				if(row) next_state=s_5;
				else	next_state=s_3,
			end
		s_3:
			begin
				col=4;
				if(row) next_state=s_5;
				else next_state=s _4;
			end
		s_4:
			begin
				col=8;
				if(row) next_state=s_5;
				else next_state=s_0;
			end
		s_5: 
			begin
				col=15;
				if(!row) next_state=s_0;
			end
	endcase
end
always@(posedge clock or posedge reset)
	if(reset)
		state<=s_0;
	else
		state<=next_state;
endmodule

module synchronizer(s_row,row,clock,reset);
 output s_row;
 input [3:0] row;
 input clock,reset;
 reg a_row,s_row;
always@(negedge clock or posedge reset)
	begin 
		if(reset)
			begin
				a_row<=0;
				s_row<=0;
			end
		else
			begin
				a_row<=(row[0]llrow[1]llrow[2]llrow[3]);
				s row<=a row;
			end
endendmodule

//模拟键盘产生信号
module row_signal(row,key,col);
 output [3:0] row;
 input [15:0] key;
 input[3:0] col;
 reg[3:0] row;
 always@(key or col)
 	begin
		row[0]=key[0]&&col[0]||key[1]&&col[1]||key[2]&&col[2]||key[3]&&col[3];
		row[1]=key[4]&&col[0]||key[5]&&col[1]||key[6]&&col[2]||key[7]&&col[3];
		row[2]=key[8]&&col[0]||key[9]&&col[1]||key[10]&&col[2]||key[11]&&col[3];
		row[3]=key[12]&&col[0]||key[13]&&col[1]key[14]&&col[2]||key[15]&&col[3];
	end
endmodule


//Testbench
module hex_keypad_grayhill_tb;
 wire [3:0] code;
 wirevalid;
 wire [3:0] col;
 wire [3:0] row;
 reg clock;
 reg reset;
 reg [15:0] key;
 integer j,k;
 reg [39:0] pressed;
 parameter [39:0] key_0="key_0";
 parameter [39:0] key_1="key_1";
 parameter [39:0] key_2="key_2";
 parameter [39:0] key_3="key_3";
 parameter [39:0] key_4="key_4";
 parameter [39:0] key_5="key_5";
 parameter [39:0] key_6="key_6";
 parameter [39:0] key_7="key_7";
 parameter [39:0] key 8="key 8";
 parameter [39:0] key_9="key_9";
 parameter [39:0] key_A="key_A";
 parameter [39:0] key_B="key_B";
 parameter [39:0] key_C="key_C";
 parameter [39:0] key_D="key_D";
 parameter [39:0] key_E="key_E";
 parameter [39:0] key_F="key_F";
 parameter [39:0] None="None";
 keypad U1(.clock(clock),.reset(reset),.row(row),
 		  .code(code),.vaild(vaild),.col(col));		//top module
 row_signal U2(.row(row),.key(key),.col(col)); 	// Simulatesignal generation
 
 always@(key)
 	begin
 		case(key)
 			16'h0000: pressed=None;
 			16'h0001: pressed=key_0;
 			16'h0002: pressed=key_1;
 			16'h0004: pressed=key_2;
 			16'h0008: pressed=key_3;
 			16'h0010: pressed=key_4;
 			16'h0020: pressed=key_5;
 			16'h0040: pressed=key_6;
 			16'h0080: pressed=key_7;
			16'h0100: pressed=key_8;
			16'h0200: pressed=key_9;
			16'h0400: pressed=key_A;
			16'h0800: pressed=key_B;
			16'h1000: pressed=key_C;
			16'h2000: pressed=key_D;
			16'h4000: pressed=key_E;
			16'h8000: pressed=key_F;
			default: pressed=None;
		endcase
	end
initial #2000 $stop;
initial 
	begin 
		clock=0;
		forever #5 clock=~clock;
	end
initial 
	begin 
		reset=1;
		#10 reset=0;
	end
initial 
	begin 
		for(k=0;k<=1;k=k+1)
			begin 
				key=0;
				#20 for(j=0;j<=16;j=j+1)
							begin
								#20 keyli]=1;
								#60 key=0;
							end
			end
	end
endmodule

2.8 log函数的Verilog设计

log函数是一种典型的单目计算函数,与其相应的还有指数函数、三角函数等。对于单目计算函数的硬件加速器设计一般两种简单方法:一种是查找表的方式;一种是使用泰勒级数展开成多项式进行近似计算。这两种方式在设计方法和精确度方面有很大的不同。查找表方式是通过存储器进行设计,设计方法简单,其精度需要通过提高存储器深度实现,在集成电路中占用面积大,因此着这种方式通常在精度要求不高的近似计算中使用。泰勒级数展开方式采用乘法器和加法器实现,可以通过增加展开级数提高计算精确度。
例:用Verilog HDL设计采用查找表方式的log函数,输入信号位宽4bits,输出信号位宽8bits
Verilog学习笔记(5):Verilog高级程序设计
其中输入数据为一位整数位三位小数位精确到2-3,输出结果两位整数位六位小数位精确到2-6。其Verilog程序代码是:

module log_lookup(x,clk,out);
 input [3:0] x;
 input clk;
 output [7:0] out;
 reg [7:0] out;
 always@(posedge clk)
 	begin
		case(x)
			4b1000:out<=8b00000000;
			4b1001:out<=8b00000111;
			4b1010:out<=8b00001110;
			4b1011:out<=8b00010101;
			4b1100:out<=8b00011001;
			4b1101:out<=8b00100000;
			4b1110:out<=8b00100100;
			4b1111:out<=8b00101000
			default:out<=8'bz;
		endcase
	end
endmodule

module log_lookup_tb;
 reg clk;
 reg [3:0]x;
 wire [7:0] out;
 initial
	begin
		x=4'b1000;
		clk=1'b0;
		repeat(7)
		#10 x=x+1;
	end
	always #5 clk=~clk;
  log_lookup U1(.x(x),.ck(clk),.out(out));
 endmodule

Verilog学习笔记(5):Verilog高级程序设计
例:用Verilog设计采用泰勒级数展开方式的log函数,输入信号位宽4bits,输出信号位宽8bits
泰勒级数的定义:若函数f (x) 在点的某一邻域内具有直到 (n+1)阶导数,则在该邻域内f (x) 的阶泰勒公式为:
Verilog学习笔记(5):Verilog高级程序设计
泰勒级数可以将一些复杂的函数用多项式相加的形式进行近似,从而简化其硬件实现。logax在x0=b处的泰勒展开为:
Verilog学习笔记(5):Verilog高级程序设计
误差范围为:
Verilog学习笔记(5):Verilog高级程序设计
在x0=1处展开为:
Verilog学习笔记(5):Verilog高级程序设计
误差范围:
Verilog学习笔记(5):Verilog高级程序设计
电路结构图如下:
Verilog学习笔记(5):Verilog高级程序设计
上述的log函数在X=1处展开,并且要求X的取值范围为1<X<2,输入4位二进制数据X精确到2-3,其中一位整数位四位小数位,输出8位二进制数据精确到2-6,其中两位整数位六位小数位。设计当中所用到的乘法器和减法器均采用前文所给出的减法器和乘法器。

module log(x,out);
 input[3:0] x;
 output[7:0] out;
 wire [3:0] out1;
 wire [7:0] out2,out3, out5, out;
 wire [3:0] out4;
 assign out4={out3[7:4]};
 assign out1=x-4'b1000;
 wallace U1(.x(out1),.y(4'b0111),.out(out2));
 wallace U2(.x(out1),.y(out1),.out(out3));
 wallace U3(.x(out4),.y(4'b011),.out(out5)); 
 assign out=out2-out5;
endmodule

module log_tb;
 reg [3:0] x=4'b1000
  wire [7:0] out;
  log U1(.x(x),.out(out));
  always
 	 #10 x=x+1;
  always@(x)
  	begin
  		if(x==4'b0000)
  			$stop;
	end
endmodule

Verilog学习笔记(5):Verilog高级程序设计

2.9 CORDIC算法的Verilog实现

坐标旋转数字计算机CORDIC(Coordinate Rotation Digital Computer)算法,通过移位和加减运算,能递归计算常用函数值,如sin, cos,sinh,cosh等函数,最早用于导航系统,使得矢量的旋转和定向运算不需要做查三角函数表、乘法、开方及反三角函数等复杂运算。J.Walther在1971年用它研究了一种能计算出多种超越函数的统一算法引入参数m将CORDIC实现的三种迭代模式:三角运算、双曲运算和线性运算统一于同一个表达式下。形成目前所用的CORDIC算法的最基本的数学基础。该算法的基本思想是通过一系列固定的、与运算基数相关的角度不断偏摆以逼近所需的旋转角度。可用下列等式进行描述。
Verilog学习笔记(5):Verilog高级程序设计
提出,从而得到
Verilog学习笔记(5):Verilog高级程序设计
这里取,所有迭代的角度的这里,这里矩阵中的所以矩阵就变为:
Verilog学习笔记(5):Verilog高级程序设计
上式中的。随着迭代次数的增加,改式就会收敛为一个常数:
Verilog学习笔记(5):Verilog高级程序设计
k作为一个常数增益,可以暂不考虑,这时上式就会变为:
Verilog学习笔记(5):Verilog高级程序设计
如果用Z来表示相位累加的部分和,则
Verilog学习笔记(5):Verilog高级程序设计
若想使Z旋转到0,则Sn的符号由Zn,来确定,如下:
Verilog学习笔记(5):Verilog高级程序设计
旋转后的最终结果为:
Verilog学习笔记(5):Verilog高级程序设计
对于一组特殊的初始值:
Verilog学习笔记(5):Verilog高级程序设计
得到的结果为:
Verilog学习笔记(5):Verilog高级程序设计
将这种工作模式称为旋转工作模式通过旋转模式就可以求出一个角度的sin和cos值。

迭代结构
简单地将CORDIC算法的公式复制到硬件描述上,就可以实现迭代的CORDIC算法,其结构如下图所示。
Verilog学习笔记(5):Verilog高级程序设计
流水线结构
流水线结构虽然比迭代结构占用的资源多,但是它大大的提高了数据的吞吐率。流水线结构是将迭代结构展开,因此n个处理单元中的每个都可以同时并行处理一个相同的迭代运算。其结构如下图所示。
Verilog学习笔记(5):Verilog高级程序设计
例:用Verilog HDL设计基于7级流水结构求正余弦的CORDIC算法在CORDIC算法中有一个初始的X、Y值。输入变量Z是角度变量,首先将X、Y输入到固定移位次数的移位寄存器进行移位,然后将结果输入到加/减法器,并且根据角度累加器的输出结果来确定加减法器的加减操作,这样就完成了一次迭代,将此次迭代运算的结果作为输入传送到下一级的迭代运算,将迭代运算依次进行下去,当达到所需要的迭代次数(本例为7次) 的时候将结果输出,此时就是想要的结果所以整个CORDIC处理器就是一个内部互联的加/减法器阵列。
Verilog学习笔记(5):Verilog高级程序设计

module sincos(clk,rst_n,ena,phase_in,sin_out,cos_out,eps);
 parameter DATA_WIDTH=8;
 parameter PIPELINE=8;
 input clk;
 inpu trst_n;
 input ena;
 input [DATA _WIDTH-1:0] phase_in;
 output [DATA_WIDTH-1:0] sin_out;
 output [DATA_WIDTH-1:0] cos_out;
 output [DATA_WIDTH-1:0] eps;
 reg [DATA_WIDTH-1:0] sin_out;
 reg [DATA_WIDTH-1:0] cos_out;
 reg [DATA_WIDTH-1:0] eps;
 reg[DATA_WIDTH-1:0] phase_in_reg;
 reg [DATA_WIDTH-1:0] x0,y0,z0;
 wire [DATA_WIDTH-1:0] x1,y1,z1;
 wire [DATA_WIDTH-1:0] x2,y2,z2;
 wire [DATA_WIDTH-1:0] x3,y3,z3;
 wire [DATA_WIDTH-1:0] x4,y4,z4;
 wire [DATA_WIDTH-1:0] x5,y5,z5;
 wire [DATA_WIDTH-1:0] x6,y6,z6;
 wire [[DATA_WIDTH-1:0] x7,y7,z7;

 reg [1:0] quadrant[PIPELINE:0];
 integer i;
 always@(posedge clk or negedge rst n)
 	begin
		if(!rst_n)
			phase_in_reg<=8b0000_0000;
		else
			if(ena)
				begin
					case(phase_in[7:6])
						2b00:phase_in_reg<=phase_in;
						2b01:phase_in_reg<=phase_in-8'h40;
						2b10:phase in reg<=phase_in-8'h80;
						2b11:phase_in_reg<=phase_in-8hc0;
					endcase
			end
	end
always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			begin
				x0<=8b00000000;
				y0<=8b00000000;
				z0<=8b00000000;
			end
		else
			if(ena)
				begin
					x0<=8'h4D;
					y0<=8'h00;
					z0<=phase_in_reg;
				end
	end
lteration #(8,0,8'h20)u1(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x0),.y0(y0),.z0(z0),.x1(x1),.y1(y1),.z1(z1));
lteration #(8,1,8'h12)u2(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x1),.y0(y1),.z0(z1),.x1(x2),.y1(y2),.z1(z2));
lteration #(8,2,8'h09)u3(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x2),.y0(y2),.z0(z2),.x1(x3),.y1(y3),.z1(z3));
lteration #(8,3,8'h04)u4(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x3),.y0(y3),.z0(z3),.x1(x4),.y1(y4),.z1(z4));
lteration #(8,4,8'h02)u5(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x4),.y0(y4),.z0(z4),.x1(x5),.y1(y5),.z1(z5));
Iteration #(8,5,8'h01)u6(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x5),.y0(y5),.z0(z5),.x1(x6),.y1(y6),.z1(z6));
Iteration #(8,6,8'h00)u7(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x6),.y0(y6),.z0(z6),.x1(x7),.y1(y7),.z1(z7));
always@(posedge clk or negedge rst _n)
	begin
		if(!rst_n)
			for(i=0;i<=PIPELINE;i=i+1)
				quadrant[i]<=2'b00;
		else
			if(ena)
				begin
					for(i=0;i<=PIPELINE;i=i+1)
						quadrant[i+1]<=quadrant[i];
						quadrant[0]<=phasein[7:6];
				end
	end
always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			begin
				sin_out<=8'b00000000;
				cos_out<=8'b00000000;
				eps<=8'b00000000;
			end
		else
			if(ena)
				case(quadrant[7])
					2'b00:
						begin
							sin_out<=y6;
							cos_out<=x6;
							eps<=z6;
						end
					2b01:
						begin
							sin_out<=x6;
							cos_out<=~(y6)+1'b1;
							eps<=z6;
						end
					2b10:
						begin
							sin_out<=~(y6)+1b1;
							cos_out<=~(x6)+1b1;
							eps<=z6
						end
					2'b11:
						begin
							sin_out<=~(x6)+1'b1;
							cos_out<=y6;
							eps<=z6;
						end
				endcase
	end
endmodule

//迭代模块:
module lteration(clk,rst_n,ena,x0,y0,z0,x1,y1,z1):
 parameter DATA_WIDTH=8;
 parameter shift=0;
 parameter constant=8"h20;
 input clk,rst_n,ena;
 input [DATA_WIDTH-1:0] x0,y0,z0;
 output[DATA_WIDTH-1:0] x1,y1,z1;
 reg [DATA_WIDTH-1:0] x1,y1,z1;
always@(posedge ck or negedge rst_n)
	begin
		if(!rst_n)
			begin
				x1<=8'b00000000;
				y1<=8'b00000000
				z1<=8'b00000000
			end
		else
			if(ena)
				if(z0[7]==1'b0)
					begin
						x1<=x0-{{shift{y0[DATA_WIDTH-1]}},y0[DATA_WIDTH-1:shift]};
						y1<=y0+{{shift{x0[DATA_WIDTH-1]},x0[DATA_WIDTH-1:shift]};
						z1<=z0-constant;
					end
			else
				begin
					x1<=x0+{{shift{y0[DATA_WIDTH-1]}},y0[DATA_WIDTH-1:shift]};
					y1<=y0-{{shift{x0[DATA_WIDTH-1]},x0[DATA_WIDTH-1:shift]};
					z1<=z0+constant;
				end
	end
endmodule

module sincos tb;
	reg clk,rst_n,ena;
	reg [7:0] phase_in;
	wire [7:0] sin_out,cos_out,eps;
	sincos U1(.clk(clk),.rst_n(rst_n),.ena(ena),.phase_in(phase_in),
			  .sin_out(sin_out),.cos_out(cos_out),.eps(eps));
	initial
		begin
			clk=0;rst_n=0;ena=1;
			phasei_n=8b00000000;
			#3 rst_n=1;
		end
	always #5 clk=~clk;
	always #10
	phase_in=phase_in+1;
endmodule

Verilog学习笔记(5):Verilog高级程序设计

3.总线控制器设计

3.1UART接口控制器

串口也称作UART(Universal Asynchronous Receiver/Transmitters),在实际应用中,通常只用TXD和RXD两个脚,而其它的管脚都不使用,UART接口时序如图所示:
Verilog学习笔记(5):Verilog高级程序设计
一个简单的UART结构图如下图所示:
Verilog学习笔记(5):Verilog高级程序设计
发送模块:发送模块的功能是将数据以串行的形式发送出去,并且将每一组的串行数据加上开始位和停止位。当byte_ready信号有效时数据被载入移位寄存器并添加开始位(低电平)和停止位(高电平)。当byte_ready信号无效时移位寄存器开始移位操作将数据以串行的形式发送出去。

module UART_transmitter(clk,reset,byte_ready,data,TXD);
 inputclk,reset;
 input byte_ready;
 input[7:0] data;
 output TXD;
 reg [9:0] shift_reg;
assign TXD=shift_reg[0];
always@(posedge clk or negedge reset)
	begin
		if(!reset)
			shift reg<=10'b1111111111;
		else 
			if(byte_ready)
				shift reg<=(1'b1,data,1'b0);
			else
				shift reg<=(1'b1,shift reg[9:1]);
endmodule

接收模块:接收模块的功能是接收发送模块输出的串行数据,并以并行的方式将数据送入存储器。当接收模块检测到开始位(低电平) 时开始接收数据,并且输入串行数据存入移位寄存器,当接收完成时将数据并行输出。

module UART_receiver(clk,reset,RXD,data_out);
 parameter idle=2'b00;
 parameter receiving=2'b01;
 inputclk,reset;
 input RXD;
 output [7:0] data out;
 reg shift;
 reg inc_count;
 reg [7:0] data_out;
 reg[7:0] shift_reg;
 reg(3:0] count;
 reg[2:0] state,next state;
 always@(state or RXD or count)
 	begin
		shift=0;
		inc_count=0;
		next_state=state,
		case(state)
			idle:
				if(!RXD)
					next state=receiving;
			receiving:
				begin
					if(count==8)
						begin
							data_out=shift_reg;
							next_state=idle;
							count=0;
							inc_count=0;
						end
					else
						begin
							inc_count = 1;
							shift=1;
						end
				end
			default:next_state<=idle;
		endcase
	end
 always@(posedge clk or negedge reset)
	begin
		if(!reset)
			begin
				data_out<=8'b0;
				count<=0;
				state<=idle;
			end
		else 
			begin
				state<=next_state;
				if(shift)
					shift_reg<={shift_reg[6:0],RXD):
				if(inc_count)
					count<=count+1;
			end
	end
endmodule

module UART_tb;
 reg clk,reset;
 reg [7:0] data;
 reg byte_ready;
 wire [7:0] data_out;
 wire serial_data
 initial
 	begin
 		clk=0;
		reset=0;
		byte_ready=0;
		data=8'b10101010;
		#40 byte_ready=1;
		#50 reset=1;
		#170 byte_ready=0:
	end
 always #80 clk=~clk
 UART transmitterU1(.clk(clk),.reset(reset),.byte ready(byte_ready),
					.data(data),.TXD(serial data));UART receiverU2(.clk(clk),
					.reset(reset),.RXD(serial data),.data out(data out));
endmodule

Verilog学习笔记(5):Verilog高级程序设计

3.2SPI接口控制器

串行外设接口(Serial PeripheralInterface SPI) 是一种同步串行外设接口,能够实现在微控制器之间或微控制器与各种外设之间以串行方式进行通信数据交换。SPI可以共享,便于组成带多个SPI接口器件的系统,且传送速率高,可编程,连接线少,具有良好的扩展性,是一种优秀的同步时序电路SPI总线通常有4条线:串行时钟线 (SCLK)、主机输入/从机输出数据线MISO)、主机输出/从机输入数据线(MOSI)。低电平有效从机选择线 (SS N)。SPI系统可分为主机设备和从机设备两大类,主机提供SPI时钟信号和片选信号,从机是接收SPI信号的任何集成电路。当SPI工作时,在移位寄存器中的数据逐位从输出引脚(MOSI)输出,同时从输入引脚(MISO)逐位接收数据。发送和接收数据操作都受控于SPI主设备时钟信号(SCLK),从而保证了同步。因此只能有一个主设备,但可以有多个从设备,可以通过片选信号 (SSN)可同时选中一个或多个从设备。
其典型结构如下图:
Verilog学习笔记(5):Verilog高级程序设计
SPI总线典型时序图如下图:
Verilog学习笔记(5):Verilog高级程序设计
例:采用Verilog设计一个简化的SPI接收机,用来完成8bits数据的传输SPI接收机框图如图所示:
Verilog学习笔记(5):Verilog高级程序设计

module SPI(sdout,MISO,sclk,srst,sen,ss_n);
 output [7:0] sdout;
 output ss_n;
 input MISO,sclk,srst,sen;
 reg [2:0] counter;
 reg [7:0] shift regist;
 reg ss_n;
 always @(posedge sclk)
 	if (!srst)
		counter<=3'b000;
	else if(sen)
		if (counter==3'b111)
			begin
				counter<=3'b000:
				ss_n<=1'b1;
			end
		else
			begin
				counter<=counter+1;
				ss_n<=1'b0;
			end
	else
		counter<=counter;
 always@(posedge sclk)
 	if(sen)
		shift_regist<={shift_regist[6:0],MISO};
	else
		shift_regist<=shift_regist;
	assign sdout=ss_n?shift_regist:8'b00000000;
endmodule

代码中,sclk为接口时钟。srst为清零信号,低电平有效。sen为接口使能信号,高电平有效。ss_n为片选信号,选择从设备,高电平有效当电路上电时,首先将清零信号置为有效,初始化电路。当sen使能信号有效后,开始传输数据,由于传输数据为8bits,因此sen使能信号应至少保持8个时钟周期。当8bits数据全部输入后,片选信号ss n有效,选择从设备,将数据整体输出。片选信号ss n由3bits计数器产生,当计数器计数到111状态时,ss_n=1,其它状态下ss_n=0文章来源地址https://www.toymoban.com/news/detail-503562.html

module SPI_tb;
 reg MISO,sclk,sen,srst;
 wire [7:0] sdout;
 wire ss_n;
 SPI U1(.sdout(sdout),.MISO(MISO),.sclk(sclk),.srst(srst),.sen(sen),ss_n(ss_n));
 initial
	begin
		MISO=0;sclk=0;srst=0:sen=0;
		#10 srst=1;
		#10 sen=1;
		#80 sen=0;
		#10 sen=1;
		#80 sen=0;
	end
initial
	begin
		#30 MISO=1;
		#10 MISO=0;
		#10 MISO=1;
		#10 MISO=0:
		#10 MISO=1;
		#10 MISO=0;
		#10 MISO=1;
		#20 MISO=1;
		#10 MISO=0;
		#10 MISO=1;
		#10 MISO=0;
		#10 MISO=1;
		#10 MISO=0;
		#10 MISO=1;
		#10 MISO=0;
	end
always #5 sclk<=~sclk;
endmodule

来源:蔡觉平老师的Verilog课程

到了这里,关于Verilog学习笔记(5):Verilog高级程序设计的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 《Java面向对象程序设计》学习笔记——CSV文件的读写与处理

    ​笔记汇总: 《Java面向对象程序设计》学习笔记 笔记记录的不是非常详实,如果有补充的建议或纠错,请踊跃评论留言!!! CSV 是英文 comma-separated values 的缩写,翻译为 “逗号分隔值“。 CSV 文件可以理解为以带逗号分隔(也可以是其他简单字符分割)的纯文本形式存储

    2024年02月10日
    浏览(52)
  • 《Java面向对象程序设计》学习笔记——第 12 章 输入流与输出流

    ​笔记汇总: 《Java面向对象程序设计》学习笔记 File 对象主要用来获取文件本身的一些信息,例如文件所在的目录、文件的长度和文件的读/写权限等,不涉及对文件的读/写操作。 创建 File 对象的构造方法有以下 3 个: 其中, filename 是文件名字 ,directoryPath 是文件的路径,

    2024年02月11日
    浏览(41)
  • 【高级程序设计语言C++】特殊类设计

    拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。 用C++11的话,可以使用特殊的语法来实现一个不能被拷贝的类。在C++11中,可以使用删除函数(deleted function)来禁用拷

    2024年02月10日
    浏览(51)
  • UNIX网络编程卷一 学习笔记 第三十章 客户/服务器程序设计范式

    开发一个Unix服务器程序时,我们本书做过的进程控制: 1.迭代服务器(iterative server),它的适用情形极为有限,因为这样的服务器在完成对当前客户的服务前无法处理已等待服务的新客户。 2.并发服务器(concurrent server),为每个客户调用fork派生一个子进程。传统上大多U

    2024年02月09日
    浏览(50)
  • 【Java高级程序设计】注解实验

    @Label注解: person类:  PersonAction接口: PersonInput类,负责提示录入人员的相关属性,提示必须是注解@Label所标注的中文名称。 PersonDisplay,负责显示人员信息,显示时的属性名称必须为注解@Label所标注的中文名称 测试类: 运行结果: @Column注解 Person类: MySqlDAO类: CreateSQL类

    2024年02月07日
    浏览(59)
  • 【高级程序设计语言C++】初识模板

    概念: 函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。 具体格式: templatetypename T1, typename T2,…,typename Tn 返回值类型 函数名(参数列表){} 输出结果: typename是用来定义模板参数,也可以使用class(切记

    2024年02月15日
    浏览(46)
  • SCAU高级语言程序设计OJ

    1018 数的排序 Description 由键盘输入三个整数a、b、c,按从小到大的顺序输出这三个数。 1016 字符变换 Description 由键盘输入5个字符,将其中的大写字符变成小写(其它类型的字符不变),最后,按输入顺序输出这5个字符。 1019 数的整除 Descrption 由键盘输入5个整数,逐个判断它

    2024年02月04日
    浏览(48)
  • 【高级语言程序设计(一)】第 7 章:指针

    目录 一、指针和指针变量 (1)指针  (2)指针变量 (3)指针变量的定义 (4)指针变量的初始化 (5)指针变量的一般使用 ① 给指针变量赋值 ② 直接使用指针变量 ③ 通过指针变量引用所指向的变量 (6)指针的基本运算  ① 取地址运算符 ② 指针运算符 * ③ 取地址

    2024年02月07日
    浏览(44)
  • 【高级语言程序设计(一)】第 10 章:文件

    目录 一、文件概述 (1)文件定义 (2)文件命名 (3)文件分类 ① 按照文件的内容划分 ② 按照文件的组织形式划分 ③ 按照文件的存储形式划分 ④ 按照文件的存储介质划分 (4)文件存取方式 (5)文件系统  二、文件的打开和关闭函数 (1)文件打开函数 ①  库函数

    2024年02月08日
    浏览(45)
  • 【高级程序设计语言C++】异常与智能指针

    当我们编写程序时,可能会遇到各种错误和异常情况,例如除以零、访问无效的内存地址等。为了能够处理这些异常情况,C++提供了异常处理机制。 异常是程序在运行时发生的意外或错误情况。当异常发生时,程序会中断当前的执行流程,并跳转到异常处理代码块。异常处理

    2024年02月10日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包