硬 件 描 述 语 言 实 验 报 告
班级:
xxxxxxxx
学号:
xxxxxxxx
姓名:
xxxxxxxx
目 录 硬 件 描 述 语 言 - 0 - 实 验 报 告 - 0 - 实验一 简单组合逻辑设计 - 2 - 实验二 简单分频时序逻辑电路的设计 - 4 - 实验三 利用条件语句实现计数分频时序电路 - 7 - 实验四 阻塞赋值与非阻塞赋值的区别 - 12 - 实验五 用always块实现较复杂的组合逻辑电路 - 16 - 实验六 在Verilog中使用函数 - 21 - 实验七 在Verilog HDL中使用任务(task) - 25 - 实验八 利用有限状态机进行时序逻辑的设计 - 32 - 实验九 利用状态机实现比较复杂的接口设计 - 36 - 实验十 利用SRAM设计一个FIFO - 46 -
实验一 简单组合逻辑设计
一、 实验目的 1.掌握基本组合逻辑电路的实现方法。
2.初步了解两种基本组合逻辑电路的生成方法。
3.学习测试模块的编写。
4.通过综合和布局布线了解不同层次仿真的物理意义。
二、 实验内容 本次实验采用Verilog HDL语言设计一个可综合的数据比较器,其功能是比较数据a与数据b的结果,如果两个数据相同,则输出结果1,否则给出结果0;并写出测试模型,使其进行比较全面的测试。
三、 实验步骤 1.建立工程文件,编写模块源码和测试模块,要求测试模块对源文件进行比较全面的测试; 2.编译源码和测试模块,用测试模块对源文件进行测试,并进行仿真; 3.观察综合后生成的文件和源文件的不同点和相同点。
4.综合时采用不同的FPGA器件,观察综合后的结果有什么不同。
四、 实验代码 1.模块源码 module compare(equal, a, b); input a,b; output equal; assign equal = (a == b)?1:0; endmodule
2.测试代码 `timescale 1ns/1ns
module compare_t; reg a, b; wire equal; initial begin a=0; b=0; #100 a=0; b=1; #100 a=1; b=1; #100 a=1; b=0; #100 a=0; b=0; #100 $stop; end compare m(.equal(equal),.a(a),.b(b)); endmodule
五、 综合仿真 RTL图及仿真后波形图:
六、 思考题 1.课本练习一的测试方法二中,第二个initial块有什么用?它与第一个initial块有什么关系?
测试方法二中的第二个initial用来暂停仿真以便观察仿真波形,它与第一个initial是并行关系
2.如果在第二个initial块中,没有写出#10000或者$stop,仿真会如何进行?
如果没有写#10000,仿真会直接停止,没有$stop,仿真不会结束。
3.比较两种测试方法,哪一种更全面?
第二种测试方法更全面,测试了更多种的变换的情况。
实验二 简单分频时序逻辑电路的设计
一、 实验目的 1.掌握条件语句在简单时序模块设计中的使用; 2.掌握verilog语句在简单时序模块设计中的使用; 3.学习在Verilog模块中应用计数器; 4.学习测试模块的编写、综合和不同层次的仿真。
二、 实验内容 1.使用always块和@(posedge clk)或@(negedge clk)的结构来表述时序逻辑,设计1/2分频的可综合模型。得到如下波形图:
2.对模块进行RTL级仿真、综合后门级仿真,布局布线仿真;
三、 实验步骤 1.建立工程文件,编写模块源码和测试模块,要求测试模块能对源文件进行比较全面的测试。
2.编译源码和测试模块,用测试模块对源文件进行测试,并综合仿真。得到波形图。
3.观察综合后生成的文件和源文件的不同点和相同点。
4.记录数据并完成实验报告。
四、 实验代码 1. 模块代码 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
2.测试代码 `timescale 1ns/100ps `define clk_cycle 50 module top; reg clk,reset; wire clk_out; always #`clk_cycle clk=~clk;
initial
begin
clk=0; reset=1; #10 reset=0; #110 reset=1; #100000 $stop; end half_clk m0(.reset(reset),.clk_in(clk),.clk_out(clk_out)); endmodule
五、 综合仿真 RTL图以及仿真后波形图
六、 思考题 1.如果没有reset信号,能否控制2分频clk_out信号的相位?
如果没有reset信号,则无法控制2分频clk_out信号的相位。
2. 只用clk时钟沿的触发(即不用2分频产生的时钟沿)如何直接产生4分频、 8分频、或者16分频的时钟?
借助一个整型变量j做计数操作。
3.如何只用clk时钟沿的触发直接产生占空比不同的分频时钟?
借助一个整型变量j做计数操作,从而用clk时钟沿的触发直接产生4分频、8分频或者16分频的时钟,及产生占空比不同的分频时钟。
实验三 利用条件语句实现计数分频时序电路
一、 实验目的 1.掌握条件语句在简单时序模块设计中的使用; 2.掌握最基本时序电路的实现方法; 3.学习在Verilog模块中应用计数器; 4.学习测试模块的编写、综合和不同层次的仿真。
二、 实验内容 1.复习课本,熟悉条件语句的使用方式; 2.建立工程并编写源代码; 3.综合并布局布线仿真并分析always语句在时序逻辑中的作用; 4.学习测试模块的编写、综合和仿真。
三、 实验步骤 1.建立工程文件,编写模块源码和测试模块,要求测试模块能对源文件进行比较全面的测试; 2.编译源码和测试模块,用测试模块对源文件进行测试,并综合仿真; 3.观察综合后生成的文件和源文件的不同点和相同点; 4.综合时采用不同的FPGA器件,如Altera公司的Cyclone II系列和Stratix III系列,观察综合后的结果有什么不同。
四、 实验代码 1.模块代码 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
2.测试代码 `timescale 1ns/100ps `define clk_cycle 50 module division_top; reg F10M,RESET; wire F500K_clk; always #`clk_cycle F10M=~F10M; initial begin
RESET=1; F10M=0; #100 RESET=0; #100 RESET=1; #10000 $stop; end fdivision fdivision(.RESET(RESET),.F10M(F10M),.F500K(F500K_clk)); endmodule
五、 综合仿真 RTL图以及仿真后波形图:
六、 思考题 1.考虑如何实现任意数值分频。
任意分频代码:
module divn(clk,rst_n,o_clk); input clk,rst_n; output o_clk; parameter WIDTH = 3; parameter N = 5;
reg [WIDTH-1:0] cnt_p,cnt_n; //count_pose,count_nege reg clk_p,clk_n;
assign o_clk = (N==1)? clk : (N[0])?(clk_p&clk_n) :clk_p; //如果N=1,o_clk=clk; 如果N为偶数,o_clk=clk_p; 如果N为奇数,o_clk=clk_p & clk_n, //之所以是相与运算,是因为clk_p和clk_n两者高电平比低电平多一个clk,而两者相 //差半个clk,相与结果使o_clk占空比为50%
always @ (posedge clk or negedge rst_n) begin if(!rst_n) cnt_p<=0; else if (cnt_p==(N-1)) cnt_p<=0; else cnt_p<=cnt_p+1; end
always @ (posedge clk or negedge rst_n) begin if(!rst_n) clk_p<=0; else if (cnt_p<(N>>1))
clk_p<=0; else clk_p<=1; end
always @ (negedge clk or negedge rst_n) begin if(!rst_n) cnt_n<=0; else if (cnt_n==(N-1)) cnt_n<=0; else
cnt_n<=cnt_n+1; end
always @ (negedge clk or negedge rst_n) begin if(!rst_n) clk_n<=0; else if (cnt_n<(N>>1)) clk_n<=0; else
clk_n<=1; end
endmodule
3. 如果综合时采用不同的FPGA器件,如Altera公司的Cyclone II系列和Stratix III系列,想想综合后的结果有什么不同?
时钟分频的实现方法如果是采用行波时钟的方式(异步设计),容易造成时钟偏差,很难控制芯片内部的逻辑基本单元中的触发器的建立/保持时间,同时不同芯片的内部参数也有所不同,同一代码的时序分析结果分析得不同也很正常。
如果分频后的时钟作为后级设计的工作时钟,那么整个设计不只使用一个主时钟,而是用多个时钟来实现的话(异步设计),存在信号的跨时钟域转换问题,跨时钟域的信号如果设计不当,会采到亚稳态。
3.课后自己试着利用10MB的时钟,设计一个单周期形状的周期波形。
模块代码:
module zhouqiwave(reset,F10M,a); input reset,F10M; output a; reg a;
reg [15:0]b; always@(reset or posedge F10M) if(!reset) begin a<=0; b<=0; end else begin if(b==199) begin a<=~a; b<=b+1; end else
begin if(b==299) begin a<=~a; b<=b+1; end if(b==499) begin a<=0; b<=b+1; end else
b<=b+1; end end endmodule 测试代码:
`timescale 10ns/10ns
module zhouqiwave_tb; reg F10M,reset; wire a; always #5 F10M=~F10M;
initial begin reset=0; F10M=0; #5 reset=1; #6000 $stop; end
zhouqiwave m(.reset(reset),.F10M(F10M),.a(a)); endmodule
实验四 阻塞赋值与非阻塞赋值的区别
一、 实验目的 1.通过实验,掌握阻塞赋值与非阻塞赋值的概念与区别; 2.深入理解顺序执行和并发执行的概念。
3.了解非阻塞和阻塞赋值的不同使用场合; 4.学习测试模块的编写,综合和不同层次的仿真。
二、 实验内容 1.本次实验参照课本上的练习三,采用Verilog HDL语言描述两个模块,分别包含有阻塞和非阻塞赋值语句; 2.编写测试模块,在相同输入信号的条件下,比较阻塞与非阻塞语句的输出结果; 3.对模块进行RTL级仿真、综合后门级仿真,布局布线仿真; 4.分析阻塞赋值与非阻塞赋值的区别。
三、 实验步骤 1.仔细阅读课本,建立工程文件,编写模块源码和测试模块,要求测试模块能对源文件进行比较全面的测试; 2.编译源码和测试模块,用测试模块对源文件进行测试,并综合仿真; 3.观察综合后生成的两个电路结构图并观察仿真波形图,分析阻塞与非阻塞赋值的异同 4.综合时采用不同的FPGA器件,如Altera公司的Cyclone II系列和Stratix III系列,观察综合后的结果有什么不同。
四、 实验代码 模块代码:
a) 采用阻塞赋值 module blocking(clk,a,b,c); output [3:0] b,c; input
[3:0] a; input
clk; reg
[3:0] b,c; always @(posedge clk) begin b = a; c = b; $display(“Blocking: a = %d, b = %d, c = %d.“,a,b,c);
end
endmodule b) 采用非阻塞赋值 module non_blocking(clk,a,b,c);
output [3:0] b,c; input
[3:0] a; input
clk; reg
[3:0] b,c; always @(posedge clk) begin b <= a; c <= b; $display(“Non_Blocking: a = %d, b = %d, c = %d.“,a,b,c);
end endmodule
测试代码:
`timescale 1ns/100ps `include“./blocking.v“ `include“./non_blocking.v“ module compareTop; wire [3:0] b1,c1,b2,c2; reg [3:0] a; reg clk; initial begin clk=0; forever #50 clk=~clk;
end
initial begin a=4'h3;
$display(“____________“); # 100 a=4'h7; $display(“____________“); # 100 a=4'hf; $display(“____________“); # 100 a=4'ha; $display(“____________“); # 100 a=4'h2; $display(“____________“); # 100 $display(“____________“); $stop; End
non_blocking non_blocking(clk,a,b2,c2);
blocking blocking(clk,a,b1,c1);
endmodule
五、 综合仿真
六、 思考题 1.解释说明测试模块中forever语句后若有其他语句,是否能够执行?为什么?
不能。forever循环语句常用于产生周期性的波形,用来作为仿真测试信号。它与always不同之处在于它不能独立写在程序中,而必须写在initial块中。
2.在blocking模块中按如下两种方法,仿真与综合的结果会有什么样的变化?作出仿真波形,分析综合结果。
a) always@(posedge clk) begin c=b; b=a; end 可以实现与上面非阻塞赋值相同的赋值结果。
b) always@(posedge clk)b=a; always@(posedge clk)c=b; 有可能出现竞争现象。
实验五 用always块实现较复杂的组合逻辑电路
一、 实验目的 1.掌握用always实现较大组合逻辑电路的方法; 2.进一步了解assign与always两种组合电路实现方法的区别和注意点; 3.学习测试模块中随机数的产生和应用; 4.学习综合不同层次的仿真,并比较结果。
二、 实验内容 1.运用always语句块设计一个8位数据选择器。要求:每路输入数据与输出数据均为4位2进制数,当选择开关(至少3位)或输入数据发生变化时,输出数据也相应地变化; 2.写出测试模块,对模块的功能进行测试; 3.对模块进行RTL级仿真、综合后门级仿真,布局布线仿真。
三、 实验步骤 1.仔细阅读课本,建立工程文件,编写模块源码和测试模块,要求测试模块能对源文件进行比较全面的测试; 2.编译源码和测试模块,用测试模块对源文件进行测试,并综合仿真; 3.观察综合后生成的两个电路结构图并观察仿真波形图,分析assign与always两种组合电路实现方法的区别和注意点; 4.综合时采用不同的FPGA器件,如Altera公司的Cyclone II系列和Stratix III系列,观察综合后的结果有什么不同。
四、 实验代码 1.模块代码:
`define plus 3'd0 `define minus 3'd1 `define band 3'd2 `define bor 3'd3 `define unegate 3'd4
//通过对指令的判断,对输入数据执行相应的操作,包括加、减、与、或和求反 //无论是指令作用的数据还是指令本身发生变化,结果都要作出及时的反应。
module alu(out,opcode,a,b);
output[7:0] out;
reg[7:0] out;
input[2:0] opcode;
input[7:0] a,b; //操作数。
always@(opcode or a or b) //电平敏感的always块 begin case(opcode) `plus: out = a+b;
//加操作 `minus:
out = a-b;
//减操作 `band: out = a&b;
//求
与 `bor: out = a|b;
//求
或
`unegate: out=~a;
//求
反 default: out=8'hx;
//未收到指令时,输出任意态 endcase end endmodule
2.测试代码:
`timescale 1ns/1ns `include “./alu.v“ module alutest; wire[7:0] out; reg[7:0] a,b; reg[2:0] opcode; parameter times=5; initial begin
a={$random}%256; //Give a radom number blongs to [0,255] b={$random}%256; //Give a radom number blongs to [0,255] opcode=3'h0; repeat(times) begin
#100 a={$random}%256; //Give a radom number.
b={$random}%256; //Give a radom number.
opcode=opcode+1; end
#100 $stop; end alu alu1(out,opcode,a,b);
endmodule
五、 综合仿真
六、 思考题 1.分析用assign语句和always语句进行组合逻辑设计时有什么异同点?
verilog语言中的赋值语句有两种,一种是持续赋值语句(assign语句),另一种是过程赋值语句(always语句)。
持续赋值语句(assign语句)主要用于对wire型变量的赋值,因为wire的值不能存住,需要一直给值,所以需要用持续赋值。
过程赋值语句(always语句)主要用于reg 型变量的赋值,因为always语句被执行是需要满足触发条件的,所以always过程块里面的内容不是每时每刻都被执行,因此需要将被赋值的对象定义成寄存器类型,以便这个值能被保持住。过程赋值又分为阻塞赋值“=”和非阻塞赋值“<=”两种。
2.使用always块设计一个八功能的算术运算单元,其输入信号a和b均为4位,还有功能选择select为3位,输出信号为out(5位)。算术运算单元所执行的操作与select信号有关,具体关系如下表所列(忽略输出结果中的上溢和下溢位):
Select信号 函数的输出 3’b000 a 3’b001 a+b 3’b010 a-b 3’b011 a/b 3’b100 a%b(余数) 3’b101 a<<1 3’b110 a>>1 3’b111 (a>b)(大小副值比较) 模块代码:
`define original 3'd0 `define plus 3'd1 `define minus 3'd2 `define division 3'd3 `define remainder 3'd4 `define leftmov 3'd5 `define rightmov 3'd6 `define compare 3'd7
module mathunit(out,select,a,b); output [4:0]out; reg [4:0] out; input [2:0] select; input [3:0] a,b;
always@(select or a or b) begin case(select) ` original: out = a; `plus: out = a+b; `minus: out = a-b; `division: out = a/b; `remainder: out = a%b; `leftmov: out = a<<1; `rightmov: out = a>>1; `compare: out = a>b?a:b; default: out = 4’bx; endcase end endmodule
实验六 在Verilog中使用函数
一、 实验目的 1.了解函数的定义和在模块设计中的使用; 2.了解函数的可综合性问题; 3.了解许多综合器不能综合复杂的算术运算;
二、 实验内容 1.本次实验是Verilog HDL语言函数调用的一个简单示范; 2.本实验采用同步时钟触发运算的执行,每个clk时钟周期都会执行一次运算; 3.在测试模块中,通过调用系统任务$display及在时钟的下降沿显示计算的结果。
三、 实验步骤 1.建立工程文件; 2.参照资料编写源文件tryfunct.v与测试模块tryfuctTop.v; 3.在测试模块tryfuctTop中调用系统任务$display及在时钟的下降沿显示计算的结果。系统任务$display的示例如下:
@(negedge clk) $display( $time, “ :n=%d, result=%d “, n, result ); 调用函数仿真tryfuctTop模块的波形。
四、 实验代码 1.模块代码:
module tryfunct(clk,result,reset); output[31:0] result; input reset,clk; reg[31:0] result; always @(posedge clk)
//clk的上沿触发同步运算。
begin if(!reset)
//reset为低时复位。
result<=0; else begin result <= factorial(3); end end function [31:0] factorial;
//函数定义。
input
[3:0]
operand; reg
[3:0]
index; begin factorial = operand ? 1 : 0; for(index = 2; index <= operand; index = index + 1) factorial = index * factorial; end endfunction endmodule
2.测试代码:
`include “./tryfunct.v“ `timescale 1ns/100ps `define clk_cycle 50
module tryfuctTop; reg[3:0] n,i; reg reset,clk; wire[31:0]result; initial begin clk=0; n=0; reset=1; # 100 reset=0; # 100 reset=1; for(i=0;i<=15;i=i+1) begin #200 n=i; end #100 $stop; end always #`clk_cycle clk=~clk; tryfunct m(.clk(clk),.n(n),.result(result),.reset(reset)); endmodule
五、 综合仿真
六、 思考题 1.设计一个带控制端的逻辑运算电路,分别完成整数的平方、立方和最大数为5的阶乘的运算,要求可综合。编写测试模块,并给出各种层次的仿真波形,比较它们的不同。
模块代码:
module myfunction(clk,n,result,reset,sl); output[6:0] result; input[2:0] n; input reset,clk; input [1:0] sl; reg[6:0] result;//define input and output always @(posedge clk) begin if(!reset) result<=0; else begin case(sl) 2'd0: result<=square(n); 2'd1: result<=cubic(n); 2'd2: result<=factorial(n); endcase end end function[6:0] square; input [2:0] operand; begin square=operand*operand; end endfunction function[6:0] cubic; input [2:0] operand; begin cubic=operand*operand*operand; end endfunction function[6:0] factorial; input [2:0] operand; reg [2:0] index; begin factorial = 1 ; for(index = 2; index <= operand; index = index + 1) factorial = index * factorial; end endfunction endmodule 测试代码:
`include “./myfunction.v“ `timescale 1ns/100ps `define clk_cycle 50 module testmyfunc; reg[2:0] n; reg reset,clk; reg[1:0] sl; wire[6:0] result; parameter times=20; initial begin n=0; reset=1; clk=0; sl=0; #100 reset=0; #100 reset=1; repeat(times) begin #50 sl={$random}%3; #50 n={$random}%6; end #1000 $stop; end always #`clk_cycle clk=~clk; myfunction myfunct(.clk(clk),.n(n),.result(result),.reset(reset),.sl(sl)); endmodule
实验七 在Verilog HDL中使用任务(task)
一、 实验目的 1.掌握任务在Verilog模块设计中的应用; 2.学会在电平敏感列表的always中使用拼接操作、任务和阻塞赋值等语句,并生成复杂组合逻辑的高级方法。
二、 实验内容 利用电平敏感的always模块和一个比较两变量大小排序的任务,设计出4个(4位)并行输入数的高速排序组合逻辑。
三、 实验步骤 1.建立工程文件,编写模块源码和测试模块,要求测试模块能对源文件进行比较全面的测试; 2.编译源码和测试模块,用测试模块对源文件进行测试,并综合仿真; 3.观察使用任务后的仿真波形,并分析使用task带来的好处。
4.综合时采用不同的FPGA器件,如Altera公司的Cyclone II系列和Stratix III系列,观察综合后的结果有什么不同。
四、 实验代码 1.模块代码:
module sort4(ra,rb,rc,rd,a,b,c,d); output[3:0] ra,rb,rc,rd; input[3:0] a,b,c,d; reg[3:0] ra,rb,rc,rd; reg[3:0] va,vb,vc,vd; always @ (a or b or c or d) begin {va,vb,vc,vd}={a,b,c,d}; sort2(va,vc);
//va ?vc??? sort2(vb,vd);
//vb ?vd??? sort2(va,vb);
//va ?vb??? sort2(vc,vd);
//vc ?vd??? sort2(vb,vc);
//vb ?vc??? {ra,rb,rc,rd}={va,vb,vc,vd}; end task sort2; inout[3:0] x,y; reg[3:0] tmp; if(x>y) begin tmp=x;
x=y; y=tmp; end endtask endmodule
2.测试代码:
`timescale 1ns/100ps
`include “sort4.v“
module task_Top;
reg[3:0] a,b,c,d;
wire[3:0] ra,rb,rc,rd;
initial
begin
a=0;b=0;c=0;d=0;
repeat(5)
begin
#100 a ={$random}%15;
b ={$random}%15;
c ={$random}%15;
d ={$random}%15;
end #100 $stop; end sort4 sort4 (.a(a),.b(b),.c(c),.d(d),.ra(ra),.rb(rb),.rc(rc),.rd(rd)); endmodule
五、 综合仿真
六、 思考题 用两种不同的方法设计一个功能相同的模块,该模块能够完成四个8位二进制输入数据的冒泡排序。要求如下:
a) 第一种方法,模仿上面的例子用纯组合逻辑实现; b) 第二种,假设8位数据是按照时钟节拍串行输入的,要求用时钟触发任务的执行法,每个时钟周期完成一次数据交换的操作。
c) 比较以上两种不同方法的运行速度和消耗资源的不同。
方法一:
模块代码:
module bub(ai,bi,ci,di,ao,bo,co,do); input[7:0] ai,bi,ci,di; output[7:0] ao,bo,co,do; reg[7:0] ao,bo,co,do; reg[7:0] ar,br,cr,dr; reg[3:0] n; parameter so=4;
always @(ai or bi or ci or di) begin {ar,br,cr,dr}={ai,bi,ci,di}; for(n=so;n>1;n=n-1) bb(ar,br,cr,dr,n); {ao,bo,co,do}={ar,br,cr,dr}; end
task bb; inout[7:0] x1,x2,x3,x4; input[3:0] n; reg[7:0] temp; reg[3:0] s; if(n>0) for(s=1;s<n;s=s+1) begin case(s) 1:sort2(x1,x2); 2:sort2(x2,x3); 3:sort2(x3,x4); endcase end endtask
task sort2; inout[7:0] x,y; reg[7:0] temp; if(x>y) begin temp=x; x=y; y=temp; end endtask endmodule 测试代码:
`timescale 1ns/100ps
module buubTEST; reg[7:0] ai,bi,ci,di; wire[7:0] ao,bo,co,do;
initial begin ai=0;bi=0;ci=0;di=0; repeat(4) begin #100 ai={$random}%256; bi={$random}%256; ci={$random}%256; di={$random}%256; end #100 $stop; end
bub m0(ai,bi,ci,di,ao,bo,co,do); endmodule 方法二(时序逻辑):
模块代码:
module bub_1(in,clk,ar,br,cr,dr,ao,bo,co,do); input[7:0] in; imput clk; output[7:0] ar,br,cr,dr; reg[7:0] ao,bo,co,do; reg[7:0] ar,br,cr,dr; reg[3:0] n,s,q; parameter so=4;
initial begin n=0;s=0;q=0; end
always @(posedge clk) begin if(n<=so) begin n=n+1; ar<=in; br<=ar; cr<=br; dr<=cr; end if(n==so+1) begin n<=so+2; s<=so; q<=1; end if(s>1) begin {ao,bo,co,do}<={ar,br,cr,dr}; if(q<s) begin case(q) 1:sort2(ar,br); 2:sort2(br,cr); 3:sort2(cr,dr); endcase q<=q+1; end else begin s<=s-1; q<=1; end end end //endalways
task sort2; inout[7:0] x,y; reg[7:0] temp; if(x>y) begin temp=x; x=y; y=temp; end endtask endmodule 测试代码:
`timescale 1ns/100ps
module bubTEST_1; reg[7:0] in; reg clk; wire[7:0] ao,bo,co,do,ar,br,cr,dr;
initial begin clk=0; in=0; begin repeat(4) #100 in={$random}%256; end #100 $stop; end
always #50 clk=~clk; bub_1 m0(in,clk,ar,br,cr,dr,ao,bo,co,do); endmodule
实验八 利用有限状态机进行时序逻辑的设计
一、 实验目的 1.掌握利用有限状态机实现一般时序逻辑分析的方法; 2.掌握用Verilog编写可综合的有限状态机的标准模板; 3.掌握用Verilog编写状态机模块的测试文件的一般方法。
二、 实验内容 1.阅读书上例子,熟悉状态机的设计流程及设计思路; 2.设计一个串行数据检测器。要求是:连续4个或4个以上为1时输出为1,其他输入情况下为0; 3.建立工程并编写源代码和测试模块; 4.布局布线级仿真。
三、 实验步骤 1.建立工程并编写模块源码; 2.编写测试模块并思考模块是如何实现逐位左移循环进入状态机测试的; 3.布局布线并仿真,结合给出的状态转化图理解状态机的工作原理; 4.记录数据并完成实验报告。
四、 实验代码 1.模块代码:
module seqdet(x,z,clk,rst,state); input
x,clk,rst; output z; output[2:0] state; reg[2:0] state; wire z; parameter IDLE='d0, A='d1, B='d2,C='d3, D='d4,E='d5, F='d6,G='d7; assign
z = ( state==E && x==0 )? 1 : 0;
always @(posedge clk) if(!rst) state <= IDLE; else casex(state) IDLE : if(x==1) state <= A; A: if(x==0) state <= B; B: if(x==0) state <= C; else state <= F; C: if(x==1) state <= D; else state <= G; D: if(x==0) state <= E; else state <= A; E: if(x==0) state <= C; else state <= A; F: if(x==1) state <= A; else state <= B; G: if(x==1) state <= F; default:state=IDLE;
endcase endmodule
2.测试代码:
`timescale 1ns/1ns
`include “./seqdet.v“
module seqdet_Top;
reg clk,rst;
reg[23:0] data;
wire[2:0] state;
wire z,x;
assign x=data[23];
always #10 clk = ~clk;
always @(posedge clk)
data={data[22:0],data[23]};
initial begin
clk=0;
rst=1;
#2 rst=0;
#30 rst=1;
data ='b1100_1001_0000_1001_0100;
#500 $stop;
end
seqdet m(x,z,clk,rst,state);
endmodule
五、 综合仿真
六、 思考题 1.为什么源文件中,状态转化使用非阻塞赋值语句?如果替换成阻塞赋值语句可以吗?为什么?
在时序块的 RTL 代码中使用非阻塞赋值。
非阻塞赋值在块结束后才完成赋值操作,此赋值方式可以避免在仿真出现冒险和竞争现象。
在组合的 RTL 代码中使用阻塞赋值。
使用阻塞方式对一个变量进行赋值时,此变量的值在在赋值语句执行完后就立即改变。
使用非阻塞赋值方式进行赋值时,各个赋值语句同步执行;因此,通常在一个时钟沿对临时变量进行赋值,而在另一个时钟沿对其进行采样。
实验九 利用状态机实现比较复杂的接口设计
一、 实验目的 1.学习运用由状态机控制的逻辑开关,设计出一个比较复杂的接口逻辑; 2.在复杂设计中使用任务(task)结构,以提高程序的可读性; 3.加深对可综合风格模块的认识。
二、 实验内容 利用状态机实现一个比较复杂的接口设计。
三、 实验步骤 1.建立工程并编写模块源码; 2.编写测试模块并思考状态机是如何实现复杂的接口设计的; 3.布局布线并仿真; 4.记录数据并完成实验报告。
四、 实验代码 1.模块代码:
module writing(reset,clk,address,data,sda,ack); input reset, clk; input[7:0] data, address; inout sda;
//sda?????????ack???????????? output ack;
//????????? reg
link_write;
// link_write?????? reg[3:0] state;
//???????? reg[4:0] sh8out_state;
//???????? reg[7:0] sh8out_buf;
//?????? reg finish_F;
//??????????????? reg ack; parameter
idle=0,addr_write=1,data_write=2,stop_ack=3; parameter
bit0=1,bit1=2,bit2=3,bit3=4,bit4=5,bit5=6,bit6=7,bit7=8;
assign
sda=link_write ? sh8out_buf[7]: 1'bz;
always @(posedge clk) begin if(!reset)
//?? begin link_write
<=0;
//??????? state
<=idle; finish_F
<=0;
//?????? sh8out_state
<=idle; ack
<=0; sh8out_buf
<=0; end else
case(state) idle: begin link_write
<=0;
//??????? finish_F
<=0;
sh8out_state<=idle; ack
<=0; sh8out_buf <=address;
//????????? state
<=addr_write;
//??????? end addr_write:
//????? begin if(finish_F==0) begin shift8_out;
end
//??????? else begin sh8out_state <= idle; sh8out_buf <= data;
// ????????? state<=data_write; finish_F<=0; end end data_write:
//????? begin if(finish_F==0) shift8_out;
else
begin link_write<=0; state<= stop_ack; finish_F<=0;
ack<=1;
//???????? end end stop_ack:
//?????????? begin ack<=0; state<=idle; end endcase end
task shift8_out;
begin case(sh8out_state) idle: begin link_write<=1;
sh8out_state<=bit7; end bit7: begin link_write<=1;
sh8out_state<=bit6; sh8out_buf<=sh8out_buf<<1;
//????????????bit6? end bit6: begin sh8out_state<=bit5; sh8out_buf<=sh8out_buf<<1; end bit5: begin sh8out_state<=bit4; sh8out_buf<=sh8out_buf<<1; end bit4: begin sh8out_state<=bit3; sh8out_buf<=sh8out_buf<<1; end bit3: begin sh8out_state<=bit2; sh8out_buf<=sh8out_buf<<1; end bit2: begin sh8out_state<=bit1; sh8out_buf<=sh8out_buf<<1; end bit1: begin sh8out_state<=bit0; sh8out_buf<=sh8out_buf<<1;
//????????????LSB? end bit0: begin link_write<=0;
//??????? finish_F<=1;
//?????? end endcase end endtask endmodule
2.测试代码:
`timescale 1ns/100ps `define clk_cycle 50
module writingTop; reg reset,clk; reg[7:0]data,address; wire ack,sda;
always #`clk_cycle clk =~clk;
initial begin clk=0; reset=1; data=0; address=0; #(2*`clk_cycle) reset=0; #(2*`clk_cycle) reset=1; #(100*`clk_cycle)
$stop; end
always@(posedge
ack)//?????????????????? begin data=data+1; address=address+1; end
writing
writing(.reset(reset),.clk(clk),.data(data),.address(address),.ack(ack),.sda(sda)); endmodule
五、 思考题 1.彻底搞清楚上例,参考第二部分的第16章的实际例子,独立编写一个能实现EEPROM全部读写功能的并行转换为IIC串行总线读写信号的模块。编写完整的符合工程要求的测试模块,进行各种层次的仿真,并观察波形。
`timescale 1ns/1ns module EEPROM_WR(SDA,SCL,ACK,RESET,CLK,WR,RD,ADDR,DATA); output SCL;
//串行时钟线 output ACK;
//读写一个周期的应答信号 input
RESET;
//复位信号 input
CLK;
//时钟信号输入 input
WR,RD;
//读写信号 input[10:0] ADDR;
//地址线 inout SDA;
//串行数据线 inout[7:0] DATA;
//并行数据线 reg ACK; reg SCL; reg WF,RF;
//读写操作标志 reg FF;
//标志寄存器 reg [1:0] head_buf;
//启动信号寄存器 reg[1:0] stop_buf;
//停止信号寄存器 reg [7:0] sh8out_buf;
//EEPROM写寄存器 reg [8:0] sh8out_state;
//EEPROM 写状态寄存器 reg [9:0] sh8in_state;
//EEPROM 读状态寄存器 reg [2:0] head_state;
//启动状态寄存器 reg [2:0] stop_state;
//停止状态寄存器 reg [10:0] main_state;
//主状态寄存器
reg [7:0] data_from_rm;
//EEPROM读寄存器 reg link_sda;
//SDA 数据输入EEPROM开关
reg link_read;
//EEPROM读操作开关
reg link_head;
//启动信号开关 reg link_write;
//EEPROM写操作开关 reg link_stop;
//停止信号开关 wire sda1,sda2,sda3,sda4;
//--------------串行数据在开关的控制下有次序的输出或输入------------------- assign sda1
= (link_head)? head_buf[1]: 1'b0; assign sda2
= (link_write)? sh8out_buf[7]: 1'b0; assign sda3
= (link_stop)? stop_buf[1]: 1'b0; assign sda4
= (sda1 | sda2 | sda3); assign SDA
= (link_sda)? sda4: 1'bz; assign DATA = (link_read)? data_from_rm: 8'hzz;
//--------------------------------主状态机状态定义------------------------------------------ parameter
Idle
= 11'b00000000001, Ready
= 11'b00000000010, Write_start = 11'b00000000100, Ctrl_write = 11'b00000001000, Addr_write = 11'b00000010000, Data_write = 11'b00000100000, Read_start = 11'b00001000000, Ctrl_read
= 11'b00010000000, Data_read = 11'b00100000000, Stop
= 11'b01000000000, Ackn
= 11'b10000000000,
//-------------------------并行数据串行输出状态----------------------------- sh8out_bit7
= 9'b000000001, sh8out_bit6
= 9'b000000010, sh8out_bit5
= 9'b000000100, sh8out_bit4
= 9'b000001000,
sh8out_bit3
= 9'b000010000, sh8out_bit2
= 9'b000100000, sh8out_bit1
= 9'b001000000, sh8out_bit0
= 9'b010000000, sh8out_end
= 9'b100000000; //--------------------------串行数据并行输出状态---------------------------- Parameter sh8in_begin = 10'b0000000001, sh8in_bit7 = 10'b0000000010, sh8in_bit6 = 10'b0000000100, sh8in_bit5 = 10'b0000001000,
sh8in_bit4 = 10'b0000010000, sh8in_bit3 = 10'b0000100000, sh8in_bit2 = 10'b0001000000, sh8in_bit1 = 10'b0010000000, sh8in_bit0 = 10'b0100000000, sh8in_end = 10'b1000000000, //---------------------------------启动状态----------------------------------
head_begin = 3'b001, head_bit
= 3'b010, head_end
= 3'b100,
//---------------------------------停止状态---------------------------------- stop_begin
= 3'b001, stop_bit
= 3'b010, stop_end
= 3'b100;
parameter
YES = 1, NO = 0;
//-------------产生串行时钟scl,为输入时钟的二分频------------------- always @(negedge CLK)
if(RESET)
SCL <= 0; else
SCL <= ~SCL;
//-----------------------------主状态机程序---------------------------------- always @ (posedge CLK) if(RESET) begin
link_read <= NO; link_write <= NO; link_head <= NO; link_stop <= NO; link_sda <= NO; ACK <= 0; RF <= 0; WF <= 0; FF
<= 0; main_state <= Idle; end else begin
casex(main_state) Idle: begin link_read
<= NO; link_write <= NO; link_head
<= NO; link_stop
<= NO; link_sda
<= NO; if(WR) begin WF <= 1; main_state <= Ready ; end else if(RD) begin RF <= 1; main_state <= Ready ; end else begin WF <= 0; RF <= 0; main_state <= Idle; end end Ready:
begin link_read
<= NO; link_write <= NO; link_stop
<= NO; link_head <= YES; link_sda
<= YES;
head_buf[1:0] <= 2'b10;
stop_buf[1:0] <= 2'b01;
head_state <= head_begin; FF
<= 0; ACK
<= 0;
main_state <= Write_start; end Write_start: if(FF == 0) shift_head;
else begin sh8out_buf[7:0] <= {1'b1,1'b0,1'b1,1'b0,ADDR[10:8],1'b0}; link_head
<= NO; link_write
<= YES; FF
<= 0;
sh8out_state
<= sh8out_bit6; main_state
<= Ctrl_write; end Ctrl_write: if(FF ==0) shift8_out; else begin sh8out_state
<= sh8out_bit7; sh8out_buf[7:0] <= ADDR[7:0]; FF
<= 0; main_state
<= Addr_write; end
Addr_write: if(FF == 0) shift8_out; else begin FF <= 0;
if(WF) begin
sh8out_state
<= sh8out_bit7;
sh8out_buf[7:0] <= DATA; main_state
<= Data_write; end if(RF) begin head_buf
<= 2'b10; head_state
<= head_begin; main_state
<= Read_start; end end
Data_write:
if(FF == 0) shift8_out; else
begin
stop_state
<= stop_begin; main_state
<= Stop; link_write
<= NO; FF
<= 0; end Read_start:
if(FF == 0) shift_head; else begin
sh8out_buf<= {1'b1,1'b0,1'b1,1'b0,ADDR[10:8],1'b1}; link_head
<= NO; link_sda
<= YES; link_write
<= YES; FF
<= 0; sh8out_state
<= sh8out_bit6; main_state
<= Ctrl_read; end Ctrl_read:
if(FF == 0)
shift8_out; else
begin
link_sda
<= NO; link_write
<= NO; FF
<= 0; sh8in_state
<= sh8in_begin; main_state
<= Data_read; end Data_read:
if(FF == 0) shift8in; else begin link_stop
<= YES; link_sda
<= YES; stop_state
<= stop_bit; FF
<= 0; main_state
<= Stop; end
Stop: if(FF == 0) shift_stop; else begin ACK
<= 1; FF
<= 0; main_state <= Ackn; end
Ackn:
begin ACK
<= 0; WF
<= 0; RF
<= 0; main_state
<= Idle; end
default:
main_state <= Idle; endcase end endmodule
实验十 利用SRAM设计一个FIFO
一、 实验目的 1.学习和掌握存取队列管理的状态机设计的基本方法; 2.了解并掌握用存储器构成FIFO的接口设计的基本技术; 3.用工程概念来编写完整的测试模块,达到完整测试覆盖。
二、 实验内容 利用SRAM模型,设计SRAM读写控制逻辑,使SRAM的行为对用户表现为一个FIFO(先进先出存储器)。
三、 实验步骤
四、 实验代码 `define FIFO_SIZE 8 `include “sram.v“
// 有的仿真工具不需要加这句, 只要sram.v模块编译过就可以了 `timescale 1ns/1ns
module t; reg [7:0]
in_data;
//FIFO数据总线 reg
fiford,fifowr;
//FIFO控制信号 wire[7:0]
out_data;
wire
nfull, nempty;
//FIFO状态信号 reg
clk,rst;
wire[7:0]
sram_data;
//SRAM数据总线 wire[10:0]
address;
//SRAM的地址总线 wire
rd,wr;
//SRAM读写控制信号
reg [7:0]
data_buf[`FIFO_SIZE:0];
//数据缓存,用于结果检查 integer index;
//用于读写data_buf的指针
//系统时钟 initial
clk=0; always
#25 clk=~clk;
//测试激励序列 initial begin fiford=1; fifowr=1; rst=1; #40 rst=0; #42 rst=1; if (nempty) $display($time,“Error: FIFO be empty, nempty should be low.\n“); //------------连续写FIFO -------
index = 0; repeat(`FIFO_SIZE)
begin data_buf[index]=$random; write_fifo(data_buf[index]); index = index + 1; end if (nfull)
$display($time,“Error: FIFO full, nfull should be low.\n“); repeat(2)
write_fifo($random); #200 //----------连续读FIFO------------- index=0; read_fifo_compare(data_buf[index]); if (~nfull) $display($time,“Error: FIFO not full, nfull should be high.\n“); repeat(`FIFO_SIZE-1)
begin index = index + 1; read_fifo_compare(data_buf[index]); end if (nempty) $display($time,“Error: FIFO be empty, nempty should be low.\n“); repeat(2)
read_fifo_compare(8'bx); reset_fifo; //------写后读FIFO------ repeat(`FIFO_SIZE*2) begin data_buf[0] = $random; write_fifo(data_buf[0]);
read_fifo_compare(data_buf[0]); end //------- 异常操作----------- reset_fifo; read_fifo_compare(8'bx); write_fifo(data_buf[0]); read_fifo_compare(data_buf[0]); $stop;
end
fifo_interface
fifo_mk (.in_data(in_data), .out_data(out_data), .fiford(fiford), .fifowr(fifowr), .nfull(nfull), .nempty(nempty), .address(address), .sram_data(sram_data), .rd (rd),.wr(wr), .clk(clk),.rst(rst) );
sram m1 ( .Address (address),
.Data (sram_data),
.SRG (rd),
//SRAM读使能
.SRE (1'b0),
//SRAM片选,低有效
.SRW (wr) );
//SRAM写使能
task write_fifo; input [7:0] data; begin in_data=data; #50
fifowr=0;
//往SRAM中写数 #200
fifowr=1; #50;
end endtask
task read_fifo_compare; input [7:0] data; begin #50
fiford=0;
//从SRAM中读数
#200
fiford=1; if (out_data != data)
$display($time,“Error: Data retrieved (%h) not match the one stored (%h). \n“, out_data, data); #50;
end endtask
task reset_fifo; begin #40 rst=0; #40 rst=1; end endtask endmodule
其余代码参见实验指导。