1.2k likes | 1.38k Views
语法详细讲解 强制激励. 在一个过程块中,可以用两种不同的方式对信号变量或表达式进行连续赋值。 过程连续赋值往往是不可以综合的,通常用在测试模块中。 两种方式都有各自配套的命令来停止赋值过程。 两种不同方式均不允许赋值语句间的时间控制。 assign 和 deassign 适用于对寄存器类型的信号(例如: RTL 级上 的节点或测试模块中在多个地方被赋值的信号)进行赋值。 initial begin #10 assign top.dut.fsml.state_reg = `init_state;.
E N D
语法详细讲解强制激励 • 在一个过程块中,可以用两种不同的方式对信号变量或表达式进行连续赋值。 • 过程连续赋值往往是不可以综合的,通常用在测试模块中。 • 两种方式都有各自配套的命令来停止赋值过程。 • 两种不同方式均不允许赋值语句间的时间控制。 • assign和deassign 适用于对寄存器类型的信号(例如:RTL级上 的节点或测试模块中在多个地方被赋值的信号)进行赋值。 initial begin #10 assign top.dut.fsml.state_reg = `init_state;
语法详细讲解字符串 语法详细讲解强制激励 语法详细讲解强制激励 #20 deassign top.dut.fsml.state_reg; end • force 和 release 用于寄存器类型和网络连接类型(例如:门级扫描寄存器的输出)的强制赋值,强制改写其它地方的赋值。 initial begin # 10 force top.dut.counter.scan_reg.q=0; # 20 release top.dut.counter.scan_reg.q; end 在以上两个例子中,在10到20 这个时间段内,网络或寄存器类型的信号被强制赋值,而别处对该变量的赋值均无效。 • force的赋值优先级高于assign。 • 如果先使用assign,再使用force对同一信号赋值,则信号的值为force所赋 的值,
语法详细讲解强制激励 语法详细讲解强制激励 当执行release后,则信号的值为assign所赋 的值。 • 如果用force对同一个信号赋了几次值,再执行release,则所有赋的值均不再存在。 • 可以对信号的某(确定)位、某些(确定)位或拼接的信号,使用force和release赋值;但不能对信号的可变位使用force和release 来赋值。 • 不能对寄存器类型的信号某位或某些位使用 assign 和deassign 来赋值。
语法详细讲解建立时钟 语法详细讲解建立时钟 虽然有时在设计中会包含时钟,但时钟通常用在测试模块中。下面 三个例子分别说明如何在门级和行为级建立不同波形的时钟模型。 [例1] 简单的对称方波时钟: reg go; wire clk; nand #(period/2) ul (clk,clk,go); initial begin go=0; #(period/2) go=1; end reg clk; always begin #period/2 clk=0; #period/2 clk=1; end 注:在有些仿真器中,如果设计所用的时钟是由与其相同抽象级别的时钟模型产生的,则仿真器的性能就能得到提高。
语法详细讲解建立时钟 [例2]简单的带延迟的对称方波时钟: reg clk; initial begin clk=0; #(period) forever #(period/2) clk=!clk end reg go; wire clk; nand #(period/2) ul (clk,clk,go); initial begin go=0; #(period) go=1; end 注:这两个时钟模型有些不同,行为描述的模型延迟期间一直是低电平,而门级描述的模型开始延迟有半个周期是不确定的。
语法详细讲解建立时钟 语法详细讲解建立时钟 [例3]. 带延迟、头一个脉冲不规则的、占空比不为1的时钟: reg clk; initial begin #(period+1) clk=1; #(period/2-1) forever begin #(period/4) clk=0; #(3*period/4) clk=1; end end reg go; wire clk; nand #(3*period/4,period/4) ul(clk,clk,go); initial begin #(period/4+1) go=0; #(5*period/4-1) go=1; end 注:这两个时钟模型也有些不同,行为描述的模型一开始就有确定的电平,而门级描述的模型有延迟开始时电平是不确定的。
语法详细讲解怎样使用任务 语法详细讲解怎样使用任务 mocule bus_ctrl_tb reg [7:0] data; reg data_valid, data_rd; cpu ul(data_valid,data,data_rd); initial begin cpu_driver(8’b0000_0000); cpu_driver(8’b1010_1010); cpu_driver(8’b0101_0101); end task cpu_driver;
语法详细讲解怎样使用任务 语法详细讲解怎样使用任务 input [7:0] data_in; begin #30 data_valid=1; wait(data_rd==1); #20 data=data_in; wait(data_rd==0); #20 data=8’hzz; #30 data_valid=0; end endtask
语法详细讲解怎样使用任务 语法详细讲解怎样使用任务 endmodule 在测试模块中使用任务可以提高程序代码的效率,可以进行多次重复操作。 cpu_data clk data_valid data_read read_cpu_state wait wait data1 data2 wait data3 data4 wait
语法详细讲解存储建模 语法详细讲解存储建模 目标 • 学会使用Verilog进行存储建模。 • 学会在Verilog中进行双向口建模。
语法详细讲解存储设备建模 存储设备建模必须注意以下两个方面的问题: • 声明存储容量的大小。 • 提供对内容的访问权限,例如: 只读 读写 同步读写 多次读,同时进行一次写 多次同步读写,同时提供一些方法保证一致性
语法详细讲解简单ROM建模 my_rom_data 0000 0101 1100 0011 1101 0010 0011 1111 1000 1001 1000 0001 1101 1010 0001 1101 myrom.v `timescale 1ns/10ps module myrom(read_data,addr,read_en_); input read_en_; input [3:0] addr; output [3:0] read_data; reg [3:0] read_data; reg [3:0] mem [0:15]; initial $readmemb(“my_rom_data”,mem); always @ (addr or read_en_) if(!read_en_) read_data=mem[addr]; endmodule ROM的数据存储在另外的一个独立的文件中
语法详细讲解简单ROM建模 上面所示的ROM模型中使用二维的存储寄存器来定义存储容量。ROM中的数据保存在一个独立的文件中,如上面的右边所示。这是一种保存ROM数据的通用的方法,它可以使数据和ROM模型分开。
语法详细讲解简单RAM建模 `timescale 1ns/1ns module mymem(data,addr,read,write); inout [3:0] data; inout [3:0] addr; input read, write; reg [3:0] memory [0:15]; //4 bits, 16 words //read assign data=read? memory[addr]:4’bz; //write always @ (posedge write) memory[addr]=data; endmodule RAM模型比ROM模型稍微复杂,因为它必须具有读写能力,而进行读写时通常使用相同的数据总线,这就需要新技术来处理双向总线。当读出口没有被激活时,RAM模型不再激励总线,如果此时的总线写入变量也没有被激活,则总线进入高阻状态,这就避免了RAM中的读写竞争。
语法详细讲解简单RAM建模 上述模型是可综合的,但是许多工具只产生一系列的寄存器,这一般就需要更大的空间,从而比实际的存储器的价格更昂贵。
语法详细讲解容量可变的存储器建模 例: module scalable_ROM(mem_word,address); parameter addr_bits=8; //size of address bus parameter wordsize=8; //width of a word parameter words=(1<<addr_bits); //size of mem output [wordsize:1] mem_word; //word of memory input [addr_bits:1] address; //address bus reg [wordsize:1] mem [0:words-1]; //mem declaration //output one word of memory wire [wordsize:1] mem_word=mem[address]; endmodule
语法详细讲解容量可变的存储器建模 上述的例子演示了怎样通过设置字长和地址位数来定义一个只读存储设备。 在上例中,存储字的范围从0开始的,而不是从1开始,这是因为内存是直接通过地址线定位的,同样地,也可以用下面的方法来定义内存和定位: reg [wordsize:1] mem [1:words]; //memory starts at word 1 //address must be incremented to address all words in memory wire [wordsize:1] mem_word=mem[address+1];
语法详细讲解载入存储设备 可以通过使用一个循环或系统任务来载入存有数据的整个存储器。 • 使用循环把值赋给存储数组。 for(i=0;i<memsize;i=i+i) // initialize memory mema[i]={wordsize{1’b1}}; • 调用$readmem系统任务。 //load memory data form a file $readmemb(“mem_file.txt”,mem);
语法详细讲解怎样使用双向口 使用inout关键字声明双向口。 inout [7:0] databus; 使用双向口需要遵循下面的规则: • 一个inout口只能声明为网络类型,而不能是寄存器类型的。 因此仿真器能确定多个激励源的最终值。 • 在设计中,每次只能激活inout的一个方向。 例如:当使用总线读RAM中的数据时,同时又向RAM模 型的双向数据总线写数据,就会产生逻辑竞争,导致总 线脱离。 必须为inout口设计控制逻辑,用来保证正确的操作。
语法详细讲解怎样使用双向口 注: • 可以声明一个inout口,用来输入或输出数据。inout口默认为网络类型,不可以对网络类型的数据进行过程赋值,但可以在过程块外对寄存器数据类型进行连续赋值,或者把它与元器件相连。 • 必须为inout口设计控制逻辑,用来保证正确的操作。当把inout口做为输入口时,必须使输出逻辑失效。
b1 b2 语法详细讲解双向口建模 使用元器件 en_a_b bus_a bus_b en_b_a module bus_xcvr (bus_a,bus_b,en_a_b,en_b_a); inout bus_a,bus_b; input en_a_b,en_b_a; bufifl b1(bus_b,bus_a,en_a_b); bufifl b2(bus_a,bus_b,en_b_a); //结构模块逻辑 endmodule 当en_a_b=1时,元器件b1激活,bus_a的值传到bus_b上 当en_b_a=1时,元器件b1激活,bus_b的值传到bus_a上
语法详细讲解双向口建模 注:在上页的例子中,使用了en_a_b和en_b_a 来控制元器件bufifl,如果进行同时控制,则得不到预期的结果。
b1 b2 语法详细讲解双向口建模 使用连续赋值 en_a_b bus_a bus_b en_b_a module bus_xcvr (bus_a,bus_b,en_a_b,en_b_a); inout bus_a,bus_b; input en_a_b,en_b_a; assign bus_b=en_a_b? bus_a:’bz; assign bus_a=en_b_a? bus_b:’bz; //结构模块逻辑 endmodule 当en_a_b=1时,bus_a的值传到bus_b上 当en_b_a=1时,bus_b的值传到bus_a上
语法详细讲解双向口建模 注:在assign语句中,通过en_a_b和en_b_a控制bus_a与bus_b之间的数据交换。 如果进行同时控制,则得不到预期的结果。
语法详细讲解双向口建模 存储口建模 测试模块 RAM单元 rd 数据总线 数据 寄存 器 wr 当rd等于1时datareg的值被赋给databus module ram_cell(databus,rd.wr); inout databus; input rd,wr; reg datareg; assign databus=rd? datareg:’bz; always @(negedge sr) datareg<=databus; endmodule 当wr的下降沿到达时,databus的值被写入datareg
语法详细讲解双向口建模 注:上页存储单元在wr的下降沿到达时存入数据。上页模块在 wr处于高电平时,通过数据总线写入数据,但必须保证wr的高电平维持时间长于数据的写入时间。 在rd处于高电平时,上述存储单元通过数据总线读出数据。由于此模型为单口存储模型,因此wr和rd不能同时为高电平,否则就得不到预期的结果。
语法详细讲解第十七部分 Verilog中的高级结构 目标 • 学会怎样定义或调用任务和函数。 • 学会怎样使用命名块。 • 学会怎样使命名块和任务失效。 • 熟悉有限状态机及怎样进行有限状态机显式建模。
语法详细讲解第十七部分 Verilog中的高级结构 可以通过把代码分成小的模块或者使用任务和函数,来把一项任务 分成许多较小的、易于管理的部分,从而提高代码的重用性。 • 任务 一般用于执行调试操作,或者行为的描述硬件 可以包含时间控制(# delays, @, wait) 可以包含input, output 和 inout参数 可以调用其他的任务或函数 • 函数 一般用于计算,或者用来代替组合逻辑 不能包含任何延迟;函数在零时间执行 只能使用input参数,但可以通过函数名来返回一个值。 可以调用其他的函数,但不可以调用任务
语法详细讲解第十七部分 Verilog中的高级结构 注: • 必须在模块内调用任务和函数。 • 在任务和函数中不能声明连线类型的变量。 • 所有的输入和输出都是真正的本地寄存器类型的数据。 • 只有当任务或函数调用并执行完后,才能有返回值。例如:当任务或函数中包含一个forever循环时,就不可能有返回值。
语法详细讲解Verilog 任务 下面的任务含有时间控制和一个输入,并且指向一个模块变量,但是不包含输出、总线和内部变量,不显示任何内容。 时间控制中使用的信号(例如 clk)不必是任务的输入,这是因为输入值只向任务内部传递一次。 module top; reg clk, a, b; DUT u1(out, a, b, clk); always #5 clk=!clk; task neg_clocks; input [31:0] number_of_edges; repeat(number_of_edges)
语法详细讲解整数和实常数 语法详细讲解Verilog 任务 @(negedge clk); endtask initial begin clk=0; a=1; b=1; neg_clocks(3); //任务调用 a=0; neg_clocks(5); b=0; end endmodule 主要特征: • 任务调用是通过在Verilog模块中写入任务名来实现的。 • 任务中可以包含input, output和inout参数。
语法详细讲解Verilog 任务 • 传递给任务的参数与任务I/O声明参数的顺序相同。虽然传递给任务的参数名可以和任务内部I/O声明的参数名相同,但是为了提高任务的模块化程度,传递给任务的参数名通常是唯一的,而不使用与任务内部I/O声明的参数名相同的参数名。 • 在任务中可以使用时间控制。 • 在Verilog中,任务定义了一个新的范围。 • 使用关键字disable禁止任务。 注意:不要在程序的不同部分同时调用同一个任务。这是因为任务只有一组本地变量,同时调用两次将会导致错误。这种情况通常发生在使用时间控制的任务中。 在任务或函数中,应给在父模块中声明的变量加注释。若在其它模块中调用任务或函数,任务和函数中所使用的变量必须包含在输入/输出口列表中。
语法详细讲解Verilog 任务 下面模块中的任务含有一个双口总线和一个内部变量,但是没有输入、输出和定时控制,没有引用本模块的变量,不显示任何内容。 在任务调用时,任务参数(口)类型被视为内部寄存器类型。 parameter MAX_BITS=8; reg [MAX_BITS:1] D; task reverse_bits; inout [7:0] data; //双口总线被视为寄存器类型! integer K; for (k=0; k<MAX_BITS; K=K+1) reverse_bits [MAXBITS – (K+1)=data[K]; endtask always @ (posedge clk) reverse_bits (D); ……
语法详细讲解Verilog 任务 下面模块中的任务含有输入、输出、时间控制和一个内部变量,并且引用了一个本模块的变量,但是没有输出,不显示任何内容。 任务调用时的参数顺序应与任务定义中声明的顺序相同。 module mult(clk, a, b, out, en_mult); input clk, en_mult; input [3:0] a, b; output [7:0] out; reg [15:0] out; always @ (posedge clk) multme(a, b, out); //任务调用
语法详细讲解Verilog 任务 task muotme; //任务定义 input [3:0] xme, tome; output [7:0] result; wait (en_mult) result=xme*tome; endtask endmodule
语法详细讲解Verilog 函数 module orand(a, b, c, d, e, out); input [7:0] a, b, c, d, e; output [7:0] out; reg [7:0] out; always @ (a or b or c or d or e) out=f_or_and(a, b, c, d, e); //函数调用 function [7:0] f_or_and; input [7:0] a, b, c, d, e; if(e==1) f_or_and=(a|b)&(c|d); else f_or_and=0; endfunction endmodule
语法详细讲解Verilog 函数 虽然函数不能包含定时控制,但是可以在包含定时控制的过程块中调用函数。 在上述函数中使用了函数名f_or_and作为寄存器类型的变量。 要点 • 函数定义不能包含定时控制语句。 • 函数必须含有输出,但不能含有输出和总线口; • 一个函数只能返回一个值,该值的变量名与函数同名,数据类型默认为reg类型。 • 传递给函数参数的顺序与函数输入参数声明的顺序相同。 • 函数定义必须包含在模块定义之内。 • 函数不能调用任务,但任务可以调用函数。 • 函数使Verilog有更广阔的适用范围。
语法详细讲解Verilog 函数 • 虽然函数只能返回一个值,但是它们的返回值可以直接的赋给一个信号拼接,从而使它们有多个输出。 {o1, o2, o3, o4}=f_or_and(a, b, c, d, e);
语法详细讲解命名块 • 可以通过在关键字begin或fork后加上:〈块名〉来给块命名。 module named_blk; …… begin :seq_blk …… end …… fork : par_blk …… join …… endmodule • 可以在命名块中声明本地变量。 • 可以使用disable禁止命名块。
语法详细讲解命名块 注意: • 命名块使Verilog有更广阔的适用范围。 • 命名块的使用缩短了仿真的时间。
语法详细讲解禁止命名块和任务 module do_arith(out, a, b, c, d, e, clk, en_mult); input clk, en_mult; input [7:0] a, b, c, d, e; output [15:0] out; reg [14:0] out; always @(posedge clk) begin : arith_block //***命名块*** reg [3:0] tmp1, tmp2; //***本地变量*** {tmp, tmp2}=f_or_and(a, b, c, d, e); // 函数调用 if(en_mult) multme(tmp1, tmp2, out); //任务调用 end always @(negedge en_mult) begin //停止计算 disable multme; //***禁止任务的执行*** diable arith_block; //***禁止命名块的执行*** end //在此定义任务和函数 endmodle
语法详细讲解禁止命名块和任务 注意: • disable语句用来终止命名块或任务的执行,因此可以在执行所有的语句前,就能从命名块或任务的执行中返回。 语法: disable 〈块名〉 或 disable 〈任务名〉 • 禁止执行命名块或任务后,所有在事件队列中安排的事件都将被删除。 • 在综合中一般不支持disable语句。 在上页的例子中,只禁止命名块也可以得到预期的结果:命名块中所有的事件、任务和函数的执行都将被取消。
语法详细讲解有限状态机(FSM) 隐式FSM: • 不需要状态寄存器 • 仿真更加有效 • 只能很好的处理线形状态改变 • 大部分综合工具不支持隐式FSM state 1 state 2 state 3 state 4
语法详细讲解编译引导语句 语法详细讲解有限状态机(FSM) 显式FSM: • 结构更加复杂 • 可以很方便的用来处理默认状态 • 能够处理复杂的状态改变 • 所有的综合工具均支持显式FSM state A state A state A state A state A
语法详细讲解有限状态机(FSMs) 注意: • 在隐式 FSMs 中,无论什么时候在一个时钟周期内写数据和在在另一个时钟周期内读数据,都会创建寄存器。 • 所有的 FSMs 都必须能复位,状态改变必须与一个单一的时钟信号同步。 • 一般的,如果状态改变比较简单,又定义的比较好,而且综合工具支持隐式状态机,就可以使用隐式类型。如果状态改变比较复杂,最好使用显式类型,这样会更加有效。 • 隐式状态机应属于行为级,而不应属于RTL级,代码中主要包含循环、嵌入的定时控制,有时也含有命名事件、wait 和 disable 语句。因此,综合中一般不支持隐式机。
语法详细讲解Verilog 函数 要返回一个矢量值(超过一个位宽),可以在函数名前声明变量的位宽。在函数中,将语句放在 begin 和 end 块中。 在函数中无论多少次对函数名进行赋值,值只返回一次。下面的函数中声明了一个内部整型变量。 module foo; input [7:0] loo; output [7:0] goo; //可以从连续赋值中调用函数 wire [7:0] goo=zero_count(loo); function [3:0] zero_count; input [7:0] in_bus;
语法详细讲解Verilog 函数 integer I; begin zero_count=0; for(I=0; I<8; I=I+1) if(!in_bus[I]) zero_count=zero_count+1; end endfunction endmodule
语法详细讲解Verilog 函数 可以声明函数的返回值的类型为 integer、real 或 time, 可以在任何表达式中调用函数。 module checksub(neg, in_a, in_b); output neg; reg neg; function integer_subtr; input [7:0] in_a, in_b; subtr=in_a-in_b; //结果可能是负数 endfunction always @(a or b) if(subtr(a, b)<0) neg=1; else neg=0; endmodule
语法详细讲解Verilog 函数 可以给返回变量的每一位赋值,还可以参数化函数的大小、函数口、甚至函数的行为。 ... parameter MAX_BITS=8; reg [MAX_BITS:1] D; function [MAX_BITS:1] reverse_bits; input [7:0] data; integer K; for(K=0; K<MAX_BITS; K=K+1) reverse_bits [MAX_BITS-(K+1)]=data[K]; endfunction always @(posedge clk) D=reverse_bits(D); ...
datain = 0 0 datain = 1 1 语法详细讲解显式有限状态机 语法详细讲解显式有限状态机 module exp(out, datain, clk, rst); input clk, rst, datain; output out; reg out; ret state; always @(pasedge clk or posedge rst) if(rst) {state, out}=2’b00; else case(state) 1’b0: begin out=1’b0; if(!datain) state=1’b0; else state=1’b1; end 1’b1 begin 状态变量 case语句