设计练习

前言:
通过一个多月的学习和复习,现在对FPGA又有一个认识,在以前的基础上又更上了一步,个人认为夏宇闻讲的这本书,对于初学者学习还是有些难度的,幸好以前有些基础,不然看起来还是有些吃力的。

在前面学习的基础上,通过练习,一定能逐步掌握 Verilog HDL 设计的要点。我们可以先理解样板模块中每一条语句的作用,然后对样板模块进行综合前和综合后仿真,再独立完成每一阶段规定的练习。当十个阶段的练习做完后,便可以开始设计一些简单的逻辑电路和系统。很快我们就能过渡到设计相当复杂的数字逻辑系统。当然,复杂的数字逻辑系统的设计和验证,不但需要系统结构的知识和经验的积累,还需要了解更多的语法现象和掌握高级的 Verilog HDL 系统任务,以及与 C 语言模块接口的方法(即 PLI),这些已超出的本书的范围。

module compare(equal,a,b);
input a,b;
output equal;
	assign equal=(a==b)?1:0; //a 等于 b 时,equal 输出为 1;a 不等于 b 时,
                          //equal 输出为 0。
endmodule

上面是一个可综合的数据比较器,很容易看出它的功能是比较数据 a 与数据 b,如果两个数据相同, 则给出结果 1, 否则给出结果 0。 在 Verilog HDL 中, 描述组合逻辑时常使用 assign结构。注意 equal=(a==b)?1:0,这是一种在组合逻辑实现分支判断时常使用的格式。

module half_clk(reset,clk_in,clk_out);
	input clk_in,reset;
	output clk_out;
	reg clk_out;
	always @(posedge clk_in)
		begin
			if(!reset) clk_out=0;
			else clk_out=~clk_out;
		end
endmodule

上面是一个二分频的可综合模型

module fdivision(RESET,F10M,F500K);
	input F10M,RESET;
	output F500K;
	reg F500K;
	reg [7:0]j;
	always @(posedge F10M)
		if(!RESET) //低电平复位。
			begin
				F500K <= 0;
				j <= 0;
			end
		else
			begin
				if(j==19) //对计数器进行判断,以确定 F500K 信号是否						反转。
				begin
					j <= 0;
					F500K <= ~F500K;
				end
				else
					j <= j+1;
			end
 endmodule

上面是将 10M 的时钟分频为 500K 的时钟。 基本原理与 1/2 分频器是一样的,但是需要定义一个计数器,以便准确获得 1/20 分频

通过模块之间的调用实现自顶向下的设计

下面给出的例子是设计中遇到的一个实例,其功能是将并行数据转化为串行数据送交外部电路编码,并将解码后得到的串行数据转化为并行数据交由 CPU 处理。显而易见,这实际上是两个独立的逻辑功能,分别设计为独立的模块,然后再合并为一个模块显得目的明确、层次清晰。

module p_to_s(D_in,T0,data,SEND,ESC,ADD_100);
output D_in,T0; // D_in 是串行输出,T0 是移位时钟并给
// CPU 中断,以确定何时给出下个数据。
input [7:0] data; //并行输入的数据。
input SEND,ESC,ADD_100; //SEND、ESC 共同决定是否进行并到串
//的数据转化。ADD_100 决定何时置数。
wire D_in,T0;
reg [7:0] DATA_Q,DATA_Q_buf;
assign T0 = ! (SEND & ESC); //形成移位时钟。.
assign D_in = DATA_Q[7]; //给出串行数据。
always @(posedge T0 or negedge ADD_100) //ADD_100 下沿置数,T0 上沿移位。
	begin
		if(!ADD_100)
			DATA_Q = data;
		else
			begin
				DATA_Q_buf = DATA_Q<<1; //DATA_Q_buf 作为中介,以令综合器
				DATA_Q = DATA_Q_buf; //能辨明。
			end
	end
endmodule

在 上面中,由于移位运算虽然可综合,但是不是简单的 RTL 级描述,直接用DATA_Q<=DATA_Q<<1 的写法在综合时会令综合器产生误解。另外,在该设计中,由于时钟 T0的频率较低,所以没有象以往那样采用低电平置数,而是采用 ADD_100 的下降沿置数。

module s_to_p(T1, data, D_out,DSC,TAKE,ADD_101);
output T1; //给 CPU 中断,以确定 CPU 何时取转化
//得到的并行数据。
output [7:0] data;
input D_out, DSC, TAKE, ADD_101; //D_out 提供输入串行数据。DSC、 TAKE
//共同决定何时取数。
wire [7:0] data;
wire T1,clk2;
reg [7:0] data_latch, data_latch_buf;
assign clk2 = DSC & TAKE ; //提供移位时钟。
assign T1 = !clk2;
assign data = (!ADD_101) ? data_latch : 8'bz;
always@(posedge clk2)
	begin
		data_latch_buf = data_latch << 1; //data_latch_buf 作缓冲
		data_latch = data_latch_buf; //,以令综合器能辩明。
		data_latch[0] = D_out;
	end
endmodule

将上面的两个模块合并起来的 的源代码

module sys(D_in,T0,T1, data, D_out,SEND,ESC,DSC,TAKE,ADD_100,ADD_101);
	input D_out,SEND,ESC,DSC,TAKE,ADD_100,ADD_101;
	inout [7:0] data;
	output D_in,T0,T1;
	p_to_s p_to_s(.D_in(D_in),.T0(T0),.data(data),
					.SEND(SEND),.ESC(ESC),.ADD_100(ADD_100));
	s_to_p s_to_p(.T1(T1),.data(data),.D_out(D_out),
					.DSC(DSC),.TAKE(TAKE),.ADD_101(ADD_101));
endmodule

简单卷积器的设计
1) 明确设计任务:
在设计之前必须明确设计的具体内容。卷积器是数字信号处理系统中常用的部件。它对模拟输入信号实时采样,得到数字信号序列。然后对数字信号进行卷积运算,再将卷积结果存入RAM 中。对模拟信号的采样由 A/D 转换器来完成,而卷积过程由卷积器来实现。为了设计卷积器,首先要设计 RAM 和 A/D 转换器的 Verilog HDL 模型。在电子工业发达的国家,可以通过商业渠道得到非常准确的外围器件的虚拟模型。如果没有外围器件的虚拟模型,就需要仔细地阅读和分析 RAM 和 A/D 转换器的器件说明书,来自行编写。因为 RAM 和 A/D 转换器不是我们设计的硬件对象,所以需要的只是它们的行为模型,精确的行为模型需要认真细致地编写,并不比可综合模块容易编写。它们与实际器件的吻合程度直接影响设计的成功。在这里我们把重点放在卷积器的设计上,直接给出 RAM 和 A/D 转换器的 Verilog HDL 模型和它们的器件参数(见附录), 同学们可以对照器件手册, 认真阅读 RAM 和 A/D 转换器的 Verilog HDL
模型。对 RAM 和 A/D 转换器的 Verilog HDL 模型的详细了解对卷积器的设计是十分必要的。到目前为止,我们对设计模块要完成的功能比较明确了。总结如下:首先它要控制 AD 变换器进行 AD 变换,从 AD 变换器得到变换后的数字序列,然后对数字序列进行卷积,最后将结果存入 RAM。 下面让我们一起来设计它。
2)卷积器的设计
通过前面的练习我们已经知道,用高层次的设计方法来设计复杂的时序逻辑,重点是把时序逻辑抽象为有限状态机,并用可综合风格的 Verilog HDL 把这样的状态机描述出来。 下面我们将通过注释来介绍整个程序的设计过程。我们选择 8 位输入总线,输出到 RAM 的数据总线也选择 8 位,卷积值的高、低字节被分别写到两个 RAM 中。地址总线为 11 位。为了理解卷积器设计中的状态机,必须对 A/D 转换器和 RAM 的行为模块有深入的理解。

module con1(address,indata,outdata,wr,nconvst,nbusy,
			enout1,enout2,CLK,reset,start);
	input CLK, //采用 10MHZ 的时钟
		  reset, //复位信号
          start, //因为 RAM 的空间是有限的,当 RAM 存满后采样和卷积都会停止。
//此时给一个 start 的高电平脉冲将会开始下一次的卷积。
          nbusy; //从 A/D 转换器来的信号表示转换器的忙或闲
	output wr, //RAM 写控制信号
   		  enout1,enout2, //enout1 是存储卷积低字节结果 RAM 的片选信号
//enout2 是存储卷积高字节结果 RAM 的片选信号
		  nconvst, //给 A/D 转换器的控制信号,命令转换器开始工作,低电平有效
		  address; //地址输出
	input [7:0] indata; //从 A/D 转换器来的数据总线
	output[7:0] outdata; //写到 RAM 去的数据总线
	wire nbusy;
	reg wr;
	reg nconvst,
		enout1,
		enout2;
	reg[7:0] outdata;
	reg[10:0] address;
	reg[8:0] state;
	reg[15:0] result;
	reg[23:0] line;
	reg[11:0] counter;
	reg high;
	reg[4:0] j;
	reg EOC;
	parameter h1=1,h2=2,h3=3; //假设的系统系数
	parameter IDLE=9'b000000001, START=9'b000000010, 
		   NCONVST=9'b000000100,READ=9'b000001000, CALCU=9'b000010000,
		   WRREADY=9'b000100000,WR=9'b001000000, WREND=9'b010000000,
		   WAITFOR=9'b100000000;
	parameter FMAX=20; //因为 A/D 转换的时间是随机的,为保证按一定的频率采样,A/D
//转换控制信号应以一定频率给出。这里采样频率通过 FMAX 控制
// 为 500KHZ。
	always @(posedge CLK)
		if(!reset)
			begin
				state<=IDLE;
				nconvst<=1'b1;
				enout1<=1;
				enout2<=1;
				counter<=12'b0;
				high<=0;
				wr<=1;
				line<=24'b0;
				address<=11'b0;
		 	end
		else
			case(state)
			IDLE:if(start==1)
					begin
						counter<=0; //counter 是一个计数器,记录已
						//用的 RAM 空间
						line<=24'b0;
						state<=START;
					end
				else
					state<=IDLE;
				//START 状态控制 A/D 开始转换
			START: if(EOC)
						begin
							nconvst<=0;
							high<=0;
							state<= NCONVST;
						end
					else
						state<=START;
						//NCONVST 状态是 A/D 转换保持阶段
			NCONVST: begin
						nconvst<=1;
						state<=READ;
					end
				//READ 状态读取 A/D 转换结果,计算卷积结果
			READ: begin
					if(EOC)
						begin
							line<={line[15:0],indata};
							state<=CALCU;
						end
					else
						state<=READ;
					end
			CALCU: begin
						result<=line[7:0]*h1+line[15:8]*h2+line[23:16]*h3;
						state<=WRREADY;
				  end
//将卷积结果写入 RAM 时,先写入低字节,再写入高字节
//WRREADY 状态是写 RAM 准备状态,建立地址和数据信号
		WRREADY:begin
					address<=counter;
					if(!high) outdata<=result[7:0];
					else outdata<=result[15:8];
					state<=WR;
				end
//WR 状态产生片选和写脉冲
			WR: begin
					if(!high) enout1<=0;
					else enout2<=0;
					wr<=0;
					state<=WREND;
					end
//WREND 状态结束一次写操作,若还未写入高字节则转到 WRREADY 状
// 态开始高字节写入
			WREND:begin
					wr<=1;
					enout1<=1;
					enout2<=1;
					if(!high)
					begin
						high<=1;
						state<=WRREADY;
					end
				 else state<=WAITFOR;
				end
//WAITFOR 状态控制采样频率并判断 RAM 是否已被写满
		WAITFOR: begin
					if(j==FMAX-1)
						begin
						counter<=counter+1;
						if(!counter[11]) state<=START;
						else
							begin
								state<=IDLE;
								$display($time,"The ram is used up.");
								$stop;
							end
						end
					else state<=WAITFOR;
				end
		default:state<=IDLE;
	endcase
// assign rd=1; //RAM 的读信号始终保持为高
//j 记录时钟,与 FMAX 共同控制采样频率
//由于直接用 CLK 的上升沿对 nbusy 判断以
//决定某些操作是否运行时,会因为两个信号
//的跳变沿相隔太近而令状态机不能正常工作。因此
//利用 CLK 的下降沿建立 EOC 信号与 nbusy 同步,相位
//相差 180 度,然后用 CLK 的上升沿判断操作是否进行。
always @(negedge CLK )
	begin
		EOC <= nbusy;
		if(!reset||state==START)
			j<=1;
		else
			j<=j+1;
	end
endmodule

程序写完后首先要做前仿真,我们可用仿真器(如 ModelSim SE/EE PLUS 来做。为检查我们写的程序,需要编写测试程序,测试程序应尽可能检测出各种极限情况。这里给出一个测试程序供参考。

`timescale 100ps/100ps
module testcon1;
	wire wr,
	enin,
	enout1,
	enout2;
	wire[10:0] address;
	reg rd,
	CLK,
	reset,
	start;
	wire nbusy;
	wire nconvst;
	wire[7:0] indata;
	wire[7:0] outdata;
	integer i;
		parameter HALF_PERIOD=1000;
//产生 10KHZ 的时钟
	initial
	begin
	rd=1;
	i=0;
	CLK=1;
	forever #HALF_PERIOD CLK=~CLK;
	end
	//产生置位信号
	initial
	begin
	reset=1;
	#(HALF_PERIOD*2 + 50) reset=0;
	#(HALF_PERIOD*3) reset=1;
	end
	//产生开始卷积控制信号
	initial
	begin
	start=0;
	#(HALF_PERIOD*7 + 20) start=1;
	#(HALF_PERIOD*2) start=0;
	#(HALF_PERIOD*1000) start=1;
	#(HALF_PERIOD*2) start=0;
	end
assign enin =1;
con1 con(.address(address),.indata(indata),.outdata(outdata),.wr(wr),
.nconvst(nconvst),.nbusy(nbusy),.enout1(enout1),
.enout2(enout2),.CLK(CLK),.reset(reset),.start(start));
sram ramlow(.Address(address),.Data(outdata),.SRW(wr),.SRG(rd),.SRE(enout1));
adc adc(.nconvst(nconvst),.nbusy(nbusy),. data(indata));
endmodule

因测试程序已经包括了各模块,只需编译测试程序并运行它。通过仿真器中的菜单(如ModelSim 仿真器中功能列表中 view 的下拉菜单选择 structure, signal 和 wave),可以根据需要看到各种信号的波形,由此检测程序
参考图

卷积器的改进
我们希望设计出快速高效的卷积器。而通过对上面设计的卷积器仿真波形的分析不难发现,有很多时间被浪费在等待 A/D 转换上。 同时因 A/D 转换, 计算卷积和写入 RAM 是串行工作的,效率很低。为提高效率我们可以采用三片 A/D 转换器同时工作,并将采样过程和计算,写入RAM 的控制改为并行工作。以下就是改进后的程序。原采样频率为 500KHZ,改进后采样频率
为 2.22MHZ,为原采样频率的四倍多。

module con3ad(indata,outdata,address,CLK,reset,start,nconvst1,nconvst2,nconvs	t3,nbusy1,nbusy2,nbusy3,wr,enout1,enout2);
	input indata,
			CLK,
			reset,
			start,
			nbusy1,
			nbusy2,
			nbusy3;
	output outdata,
			address,
			nconvst1, // 采用三根控制线控制三片 A/D 转换器
			nconvst2,
			nconvst3,
			wr,
			enout1,
			enout2;
	wire[7:0] indata;
	wire CLK,
		reset,
		start,
		nbusy1,
		nbusy2,
		nbusy3;
	reg[7:0] outdata;
	reg[10:0] address;
	reg nconvst1,
		nconvst2,
		nconvst3,
		wr,
		enout1,
		enout2;
reg[6:0] state;
reg[5:0] i;
reg[1:0] j;
reg[11:0] counter;
reg[23:0] line;
reg[15:0] result;
reg high;
reg k;
reg EOC1,EOC2,EOC3;
parameter h1=1,h2=2,h3=3;
parameter IDLE = 7'b0000001, READ_PRE = 7'b0000010,
			READ = 7'b0000100, CALCU = 7'b0001000,
			WRREADY = 7'b0010000, WR = 7'b0100000,
			WREND = 7'b1000000;
always @(posedge CLK)
	begin
		if(!reset)
			begin
				state<=IDLE;
				counter<=12'b0;
				wr<=1;
				enout1<=1;
				enout2<=1;
				outdata<=8'bz;
				address<=11'bz;
				line<=24'b0;
				result<=16'b0;
				high<=0;
			end // end of "if"
		else
			begin
			case(state)
				IDLE:if(start)
					begin
						counter<=0;
						state<=READ_PRE;
					end
					else state<=IDLE;
			READ_PRE: if(EOC1||EOC2||EOC3) //由于频率相对改进前的卷积
										//器大大提高,所以加入
										//READ_PRE 状态对取数操作
										//予以缓冲。
						state<=READ;
					else
						state<=READ_PRE;
			READ:begin
				  high<=0;
				  enout2<=1;
				  wr<=1;
				if(j==1)
					begin
						if(EOC1)
							begin
								line<={line[15:0],indata};
								state<=CALCU;
							end
						else state<=READ_PRE;
					end
			else if(j==2&&counter!=0)
				begin
					if(EOC2)
						begin
							line<={line[15:0],indata};
							state<=CALCU;
						end
				else state<=READ_PRE;
				end
			else if(j==3&&counter!=0)
				begin
					if(EOC3)
						begin
							line<={line[15:0],indata};
							state<=CALCU;
						end
				else state<=READ_PRE;
				end
				else state<=READ;
				end
		CALCU:begin
				result<=line[7:0]*h1+line[15:8]*h2+line[23:16]*h;
				state<=WRREADY;
			end
		WRREADY:begin
					wr<=1;
					address<=counter;
				if(k==1)state<=WR;
				else state<=WRREADY;
				end
			WR: begin
				if(!high) enout1<=0;
				else enout2<=0;
					wr<=0;
				if(!high) outdata<=result[7:0];
				else outdata<=result[15:8];
				if(k==1) state<=WREND;
				else state<=WR;
				end
		WREND:begin
				wr<=1;
				enout1<=1;
				enout2<=1;
				if(k==1)
					if(!high)
						begin
							high<=1;
							state<=WRREADY;
						end
					else
						begin
							counter<=counter+1;
						if(counter[11]&&counter[0])
							state<=IDLE;
						else state<=READ_PRE;
						end
					else state<=WREND;
				end
		default:state<=IDLE;
	endcase //end of the case
	end // end of "else"
	end // end of "always"
//计数器 i 用来记录时间
always @(posedge CLK)
	begin
		if(!reset) i<=0;
		else
			begin
				if(i==44) i<=0;
				else i<=i+1;
			end
	end
//j 是控制信号,协调卷积器轮流从三片 A/D 上读取数据。
always @(posedge CLK)
begin
	if(i==4) j<=2;
	else if(i==10) j<=0;
	else if(i==19) j<=3;
	else if(i==25) j<=0;
	else if(i==34) j<=1;
	else if(i==40) j<=0;
end
//k 是计数器,用以控制写操作信号
always @(posedge CLK)
	begin
		if(state==WRREADY||state==WR||state==WREND)
		if(k==1) k<=0;
		else k<=1;
		else k<=0;
	end
//根据计数器 i 控制三片 A/D 转换信号 NCONVST1,NCONVST2,NCONVST3
always @(posedge CLK)
	begin
		if(!reset) nconvst1<=1;
		else if(i==0) nconvst1<=0;
		else if(i==3) nconvst1<=1;
	end
always @(posedge CLK)
	begin
		if(!reset) nconvst2<=1;
		else if(i==15) nconvst2<=0;
		else if(i==18) nconvst2<=1;
	end
always @(posedge CLK)
	begin
		if(!reset) nconvst3<=1;
		else if(i==30) nconvst3<=0;
		else if(i==33) nconvst3<=1;
	end
always @(negedge CLK)
	begin
		EOC1<=nbusy1;
		EOC2<=nbusy2;
		EOC3<=nbusy3;
	end
endmodule

测试程序如下:

`timescale 1ns/100ps
module testcon3ad;
wire wr,
enin,
enout1,
enout2;
wire[10:0] address;
reg clk,
reset,
start;
rd;
wire nbusy1,
nbusy2,
nbusy3;
wire nconvst1,
nconvst2,
nconvst3;
wire[7:0] indata;
wire[7:0] outdata;
parameter HALF_PERIOD=15;//时钟周期为 30ns
initial
begin
clk=1;
forever #HALF_PERIOD clk=~clk;
end
initial
begin
reset=1;
#110 reset=0;
#140 reset=1;
end
initial
begin
start=0;
rd=1;
#420 start=1;
#120 start=0;
#107600 start=1;
#150 start=0;
end
assign enin=1;
con3ad con3ad(.indata(indata),.outdata(outdata),.address(address),
.CLK(clk),.reset(reset),.start(start),
.nconvst1(nconvst1),.nconvst2(nconvst2),.nconvst3(nconvst3),
.nbusy1(nbusy1),.nbusy2(nbusy2),.nbusy3(nbusy3),
.wr(wr),.enout1(enout1),.enout2(enout2));
sram ramlow(.Address(address),.Data(outdata),.SRW(wr),.SRG(rd),.SRE(enout1));
adc ad_1(.nconvst(nconvst1),.nbusy(nbusy1),. data(indata));
adc ad_2(.nconvst(nconvst2),.nbusy(nbusy2),. data(indata));
adc ad_3(.nconvst(nconvst3),.nbusy(nbusy3),. data(indata));
endmodule

在这里插入图片描述
到这里这本书我差不多看完了,这时我正好在实验室学习,也有一段时间了,还有很多关于FPGA设计的书籍,我希望以后还可以以这样的方式去记录自己的学习过程。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值