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

文章目录

  • 1.数字电路系统设计的层次化
  • 2.典型电路设计
    • 2.1加法器树乘法器
    • 2.2Wallace树乘法器
    • 2.3复数乘法器
    • 2.4 FIR滤波器设计
    • 2.5 片内存储器的设计
    • 2.6 FIFO设计
    • 2.7 键盘扫描和编码器
    • 2.8 log函数的Verilog设计
    • 2.9 CORDIC算法的Verilog实现
  • 3.总线控制器设计
    • 3.1UART接口控制器
    • 3.2SPI接口控制器
  • 来源:蔡觉平老师的Verilog课程


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

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

向量点积乘法器:
采用模块层次化设计方法,设计4维向量点积乘法器,其中向量a =(a1,a2,a3,a4);b=(b1,b2,b3,b4)。点积乘法规则:
Verilog学习笔记(5):Verilog高级程序设计_第5张图片
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高级程序设计_第6张图片

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高级程序设计_第7张图片

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

Verilog学习笔记(5):Verilog高级程序设计_第8张图片
两级流水线加法器树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高级程序设计_第9张图片

2.2Wallace树乘法器

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

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高级程序设计_第12张图片

2.3复数乘法器

在这里插入图片描述
复数乘法器的电路结构如下图所示。将复数x的实部与复数y的实部相乘,减去x的虚部与y的虚部相乘,得到输出结果的实部。将x的实部与y的虚部相乘,加上x的虚部与y的实部相乘,得到输出结果的虚部。

Verilog学习笔记(5):Verilog高级程序设计_第13张图片

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高级程序设计_第14张图片

2.4 FIR滤波器设计

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

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高级程序设计_第16张图片

2.5 片内存储器的设计

(1)RAM 的 Verilog描述
RAM是随机存储器,存储单元的内容可按需随意取出或存入。这种存储器在断电后将丢失掉所有数据,一般用来存储一些短时间内使用的程序和数据。其内部结构如下所示:
Verilog学习笔记(5):Verilog高级程序设计_第17张图片
例:用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高级程序设计_第18张图片
例:用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高级程序设计_第19张图片
(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高级程序设计_第20张图片

2.6 FIFO设计

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

一个FIFO的组成一般包括两个部分: 地址控制部分和存储数据的RAM部分。如下图所示。地址控制部分可以根据读写指令生成RAM地址。RAM用于存储堆栈数据,并根据控制部分生成的地址信号进行数据的存储和读取操作。这里的RAM采用的是前面提到的双口RAM。
Verilog学习笔记(5):Verilog高级程序设计_第22张图片
例: 用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高级程序设计_第23张图片

2.7 键盘扫描和编码器

键盘扫描和编码器用于在拥有键盘的数字系统中手工输入数据,通过检测按键是否按下,产生一个唯一对应此按键的扫描码。
例:用Verilog设计十六进制键盘电路的键盘扫描和编码器:
Verilog学习笔记(5):Verilog高级程序设计_第24张图片
控制信号状态机转移图如下图所示:
Verilog学习笔记(5):Verilog高级程序设计_第25张图片
Verilog学习笔记(5):Verilog高级程序设计_第26张图片
详情见: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高级程序设计_第27张图片
其中输入数据为一位整数位三位小数位精确到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高级程序设计_第28张图片
例:用Verilog设计采用泰勒级数展开方式的log函数,输入信号位宽4bits,输出信号位宽8bits
泰勒级数的定义:若函数f (x) 在点的某一邻域内具有直到 (n+1)阶导数,则在该邻域内f (x) 的阶泰勒公式为:
在这里插入图片描述
泰勒级数可以将一些复杂的函数用多项式相加的形式进行近似,从而简化其硬件实现。logax在x0=b处的泰勒展开为:
在这里插入图片描述
误差范围为:
在这里插入图片描述
在x0=1处展开为:
在这里插入图片描述
误差范围:
在这里插入图片描述
电路结构图如下:
Verilog学习笔记(5):Verilog高级程序设计_第29张图片
上述的log函数在X=1处展开,并且要求X的取值范围为1-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

在这里插入图片描述

2.9 CORDIC算法的Verilog实现

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

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

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高级程序设计_第33张图片

3.总线控制器设计

3.1UART接口控制器

串口也称作UART(Universal Asynchronous Receiver/Transmitters),在实际应用中,通常只用TXD和RXD两个脚,而其它的管脚都不使用,UART接口时序如图所示:
Verilog学习笔记(5):Verilog高级程序设计_第34张图片
一个简单的UART结构图如下图所示:
Verilog学习笔记(5):Verilog高级程序设计_第35张图片
发送模块:发送模块的功能是将数据以串行的形式发送出去,并且将每一组的串行数据加上开始位和停止位。当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高级程序设计_第36张图片

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高级程序设计_第37张图片
SPI总线典型时序图如下图:
Verilog学习笔记(5):Verilog高级程序设计_第38张图片
例:采用Verilog设计一个简化的SPI接收机,用来完成8bits数据的传输SPI接收机框图如图所示:
Verilog学习笔记(5):Verilog高级程序设计_第39张图片

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

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学习笔记,学习,fpga开发)