Verilog HDL入门(3)

Verilog 基础语言3

3.第三章内容

数据流模型

连续赋值语句常用来建立数据流的行为模型;过程性赋值语句用来为时序电路建立行为模型。组合逻辑电路行为模型的最好方法时使用连续赋值语句

连续赋值语句

连续赋值语句可以用来对线网进行赋值(不能用来对寄存器进行赋值),它的格式如下(简单格式)

1
2
3
4
5
6
7
assign LHS_target = RHS_expression;
举例
//线网声明:
wire [3:0]frm_rdy,coh_rdy,hrd_tag;

//连续赋值语句
assign hrd_tag = coh_rdy & frm_rdy;

连续赋值语句 被赋值目标是hrd_tag;coh_rdy & frm_rdy,特别注意,连续赋值语句种一定由关键字assign
连续赋值语句在什么时候执行,只要右侧表达式中的操作数有事件发生(既操作数值改变)时,就会计算右侧表达式;若新的结果值与原来的值不同,则把新的结果值赋给左侧的被赋值目标

若coh_rdy 或 frm_rdy 发生了变化,就会计算右侧的表达式。若右侧表达式的值发生了变化,则把新计算出的值赋给线网 hrd_tag
连续赋值的目标可以是类型: 标量线网、向量线网、矩阵中的一个元素(该矩阵可以是标量线网类型的,也可以是向量线网类型的)、向量线网的某一位、向量线网的部分位、上述各种类型的拼接体。

1
2
3
4
5
6
7
8
9
标量线网 如:wire a,b;
向量线网 如:wire [3:0] a,b;

wire [3:0] a;
//4位宽的线网
//[3:0]指定了变量的位宽为4位,其中:
//3为最高有效位(MSB),对应二进制数的第3位;
//0为最低有效位(LSB),对应二进制数的第0位
//若未显式赋值,默认值为高阻态z(无驱动时)或不定态x(多驱动冲突时)

下面,被赋值的目标是一个标量线网和一个向量线网的拼接体

1
2
3
wire carry_out,carry_in;
wire [3:0] sum,a,b; //声明了三个4位宽的线网(wire)类型变量sum、a和b
assign {carry_out,sum} = a + b + carry_in;

a和b的位宽是4位,所以加法运算能够产生最大为5位的结果。因此左侧表达式的宽度指定为5位(carry_out 1位,sum 4位),最终这个赋值语句将右侧表达式最右边(低)4位的值赋给sum,第五位(进位)的值赋值给carry_out。

例子说明如何在一条连续赋值语句中进行多次赋值

1
2
3
4
assign 	mux_out = (select == 0)?Input_a:'bz,	//'bz 表示32位位宽
mux_out = (select == 1)?Input_b:'bz,
mux_out = (select == 2)?Input_c:'bz,
mux_out = (select == 3)?Input_d:'bz;

上面赋值语句是下面4条独立连续赋值语句的简化书写格式。

1
2
3
4
assign	mux_out = (select == 0)?Input_a:'bz;
assign mux_out = (select == 1)?Input_b:'bz;
assign mux_out = (select == 2)?Input_c:'bz;
assign mux_out = (select == 3)?Input_d:'bz;

例子:

1
2
3
4
5
wire dtag;
parameter SIZE = 7;
wire [SIZE:0]padded_dtag;

assign padded_dtag = {SIZE{1'b0},dtag};

若没有对连续赋值的目标类型进行声明,则将把它默认为标量线网。

1
assign mc_noburst = dma_lock;	//没有对mc_noburst经行声明,mc_noburst被默认为1位的线网。

线网声明赋值
连续赋值可以作为线网声明的一部分,这样赋值称为线网声明赋值。

1
2
3
4
5
6
7
wire [3:0]qmv_wr = 4'b0;
wire frm_wait = 'b1;

wire ictr_gt_qctr = ictr > qctr,
qctr_gt_ictr = qctr > ictr;

wire [(8*12-1):0]dbg_dump_rpt = "dbg_dump_rpt";

线网声明赋值不但声明了线网,还对声明的线网进行连续赋值。
线网声明赋值是 :声明线网,然后编写连续赋值语句的一种简便形式。
wire wr_cycle; 等价于线网声明赋值语句 wire wr_cycle = ‘b1;
assign wr_cycle = ‘b1; ==========================>

不允许对同一线网进行多个线网声明赋值。若必须进行多个赋值,则必须使用连续赋值语句。

赋值延迟()
assign #6 dbg_data = int_data|| peg_cntxt;
#6:上述赋值语句中指定的延时,指从右侧表达式中任一操作数的变化,到右侧表达式重新计算,再把计算结果赋给左侧的目标总共需要6个时间单位的延迟,若在时刻5,int_data值发生变化,则在时刻5重新计算赋值语句的右侧表达式,并在时刻11(=5+6)把计算出的新值赋给dbg_data
以最后一次右边变化计算出的结果延时后,赋值给左边

在右侧得值传递给左侧之前,右侧得值发生了变化,会怎么样?最后一次值得变化将起决定作用,
assign #4 peg_free = xbid_par;
延时期间右侧表达式发生得变化会被滤除
(需要看书,才能理解此处)
在把右式得变化传播到左式之前,右式必须至少能够在该延迟期间保持右式值不变;
在延迟期满,若右式得值发生了变化,则前面得值就不能传播到输出。

在每个延迟得声明中,总共有3个延迟值可以被指定
上升延迟值、下降延迟值、截止延迟值
格式:
assign #(rise,fall,turn-off) LHS_target = RHS_expression;

三个延迟值为0时,如何在连续赋值语句中指定这些延迟
//一个延迟参数
assign #4 biu_par = fe_par ||wsp_par;	//上升延迟、下降延迟、截止延迟(既变化到Z的延迟)和变化到x的延时相同,都为4

//两个延迟参数
assign #(4,8)biu_par = rd_trg;	//上升延迟4、下降延迟8、截止延迟和x的延迟相同,为4和8的最小值,4

//三个延迟参数
assign #(4,8,6)fe_arb = &fe_dbus;	//上升延迟4、下降延迟8、截止延迟6 变化到x的延迟为4

//没有延迟参数
assign fe_dbus = rd_address[7:4];	//所有延迟为0

右侧表达式的值从非0向量变化到0向量,则使用下降延迟;
右侧表达式的值变化到z,则使用截止延迟,其余的情况都使用上升延迟

线网延迟
延迟可以在线网声明中定义,
wire #5 mem_write;
该延迟指的是mem_write驱动源的值发生改变到线网mem_write本身的值发生改变的延迟。

assign #2 mem_write = chunk_valid & flop_valid;
假设flop_valid 变化导致重新计算右侧表达式,结果和以前不同,则在2个时间单位后,重新赋值个mem_write;
但是mem_write的线网延迟,对线网mem_write的赋值发生在7个时间单位后,

首先赋值延迟起作用,然后加上线网延迟产生的作用;

线网声明赋值中, 指定了延迟,这个延迟不是线网延迟,是赋值延迟,
nc_data进行的线网声明赋值, 2个时间单位指的是赋值延时,不是线网延迟(线网延迟,声明线网时不能有赋值)
wire [3:0] #2 nc_data = si_data-mem_wdata;

主/从触发器
module msdff_dataflow(
	input wire d, clk,
	output wire q,q_bar
);
	wire not_clk, not_d,not_y,y,d1,d2,y_bar,y1,y2;
	assign not_d =~d;
	assign not_clk =~clk;
	assign not_y = ~y;

	assign d1 =~(d & clk);
	assign d2 = ~(clk & not_d);



endmodule




行为级建模
过程性结构
两种语句是对设计进行行为级建模的主要结构 initial 、always
模块中可以包含任意个 initial always 语句 ,这些语句相互之间是并行执行,
一条initial always 语句的执行会产生一个单独的控制流、所有的initial always 都是再0时刻开始并行的执行。

initial 语句只执行一次,在仿真开始时(0时刻)执行

parameter SIZE = 1024;
reg [7:0] vld_ram[0:SIZE-1];
reg speed_reg;

initial
	begin:	seq_blk_a
		integer		index;

		speed_reg = 0;

		for(index = 0;index < SIZE; index = index + 1'b1)
			vld_ram[index] = 0;

	end

begin ... end 划定顺序块的界限,顺序块中的语句是顺序执行的
seq_blk_a 顺序块的标签,若过程中没有出现局部声明语句,不需要标签




always语句
always语句是反复执行的, 与initial语句类似地方都是从0时刻开始执行

事件列表写成: @(a or b) @(a,b) @(*)

时序控制
延迟控制、事件控制

延迟控制
#2 tx_addr = rx_addr - 5;	//等待延迟,执行过程语句前等待2个单位延迟

延迟控制的延迟可以是任意表达式,不必限制为一个常量,

延迟表达式的值为0,则称为显式零延迟。
#0;
显示零延迟触发一个等待,等待所有其他在当前仿真时刻要被执行的事件执行完毕后,才将其恢复,仿真事件不会前进

若延迟表达式的值为 x、z,等效与零延迟。
若延迟表达式计算结果为负值,则将其二进制补码值作为延迟

若延迟值包含空格字符、表达式,需要用括把延迟值括起来


事件控制
事件控制中,语句的执行是基于事件的:跳变沿敏感事件控制、电平敏感事件控制
跳变沿敏感事件控制 @(negedge rst_n) @(posedge clk)
电平敏感事件控制 @cell_byte
@cell_byte per_frame= cell_byte; //当cell_byte上有事件发生时,才会执行赋值操作

负跳变			正跳变
1 -> x			0 -> x
1 -> z			0 -> z
1 -> 0			0 -> 1
x -> 0			x -> 1
z -> 0			z -> 1

通过一种方式可以隐含地把相应的过程性语句中所有的变量和线网都包含在敏感事件例表中。
@*表明相应的过程性语句对于其内部的任何值的变化都会敏感

always @ *
	procedural_statement

@* 把过程性语句内部所有的变量都看作是敏感事件列表的一部分
always @* cpu_reg = master_rag + control_reg;
//隐含地把 master_rag 、control_reg 包含在敏感事件列表中。实际上是下面always语句简写格式
always
	@(master_rag,control_reg)
		cpu_reg = master_rag + control_reg;

@* 代表在相应块中的任何语句中使用了的变量和线网,还包括在赋值语句中的等号左边的表达式中的序号变量
always @* w[k] = ram&chip;

等价于
@(ram,chip,k)


电平敏感事件控制
电平敏感事件控制中,知道条件变为正后,过程性语句才执行
wait(condition)  //等到 condition 为真的时候,才执行下面的语句
	procedural_statement


语句块(begin  end)
1.顺序语句块( begin   end) :语句块中的语句按照给定顺序执行
2.并行语句块( fork  join)  : 语句并行执行
语句块的表示符是可选的。
	若有标识符,可以在语句块内部声明局部变量,还可以被应用

顺序语句块
	语句按顺序执行;定界符是begin end
顺序块语法:
	begin
		[:block_id{declarations}]//{块id[声明]} 标识符
		procedural_statement(s)		//过程型语句 执行部分
	end

	begin 	//还可以这么写顺序块
		mem_r = sms|mem;

		@(negedge clk_sms)
			update = mem_rd;
	end

	begin: lbl_seq_blok	//lbl_seq_blok 标识符
		reg[1:0] dmac;
		dmac = 2'd1;
	end
	顺序块标记 lbl_seq_blok ;

并行语句块
	定界符是fork join ;并行语句块中的语句是并行执行的
	在并行语句块内的每条语句中指定的延迟值都是相对于语句块开始执行的时刻的;
	并行语句块中的最后一个行为(并不一定是最后一条语句)执行完成时,再继续执行这个并行块后面其他语句。
	在执行跳出语句块前必须执行完并行语句块内的所有语句。

	就是将并行语句块中所有的语句并行执行完成后,在执行并行块后面的语句
	fork
	[:block_id{declarations}]	//{块id[声明]} 标识符
	procedural_statement(s)
	join

	顺序语句块和并行语句块可以混合使用,下面是两者不同之处

	always
		begin :blk_seq_a
			#4 pm_write = 5;				//S1

			fork:blk_par_a					//S2
				#6 pm_select = 7;			//P1

				begin :blk_seq_b			//P2
					wdog_rst = pm_enable;	//S6
					#5 wdog_intr = wdog_rst;//S7
				end

				#2 frc_sel = 3;				//P3
				#4 pm_itcr = 2;				//P4
				#8 itop = 4;				//P5
			join

			#8 pm_sel = 3;					//S3
			#4 pcell_id = 52;				//S4
			#6 $stop;						//S5
		end
	blk_seq_a 顺序语句块,并且块内所有语句(S1、S2、S3、S4、S5)都是按照顺序执行


过程性赋值
	过程性赋值是在initial 、 always语句内进行的赋值 ,只能对变量类型赋值,

语句内部延迟
	语句内部延迟之前计算的右式,然后进入延迟等待,最后把重新计算的值赋给左式的目标

	重复事件控制的时序控制 格式:
	repeat(expression) @(event_expression)
	这种控制格式是利用一个或多个事件发生次数来指定延迟
	hresult = repeat(2) @(negedge tclk) hw_data + hr_data;
	既hw_data + hr_data的值,然后等待时钟tclk上出现2个负跳变沿,再把右边值赋给hresult。
	等价:
	begin
		temp = hw_data + hr_data;
		@(negedge tclk);
		@(negedge tclk);
		hresult = temp;
	end


阻塞性过程赋值
	赋值操作符是 = 的过程性赋值称为阻塞性过程赋值
	a = 52;
	阻塞性过程赋值语句,当前语句必须执行完成才能执行下一条赋值语句。

非阻塞性过程赋值
	非阻塞性过程赋值语句中,使用赋值符号 <=

	特殊例子
	initial
	begin
		wdog_intr <= 0;
		wdog_intr <= 1;
	end
	在initial语句执行后,wdog_intr的值为1;
	verilog HDL标准规定了对同一个reg变量的非阻塞性赋值,按照赋值语句执行的顺序来执行;
	wdog_intr先被赋值为0,然后被赋值为1

	一个在always语句里读取一个变量(key_a)的值,又在另一条always语句里面对同一个变量进行赋值,并且两条always语句由同一个时钟沿来控制, 怎么才能确定读操作发生在写操作之前,
	答案是:用非阻塞赋值可以确保读操作发生在写操作之前。

	always @(negedge clock)
		reg_a <= data;			//在此处reg_a 被赋值

	always @(negedge clock)
		reg_b <= reg_a;

	何时使用哪种过程性赋值语句?(建议)
		对always语句块外用到的变量进行赋值时,使用非阻塞性赋值(<=)
		计算中间结果的时候,用阻塞性赋值(=)

		always @(posedge clk_piol) begin
			bdir_data = ^spi_rdata;				//用阻塞性赋值计算中间结果
			spi_wdata <= bdir_data + rst_cnt;	//用非阻塞赋值对语句块外变量赋值
		end

		always @(spi_wdata) begin
			spi_pariy <= ^ spi_wdata;
		end

连续赋值(assign)与过程赋值的比较

		过程赋值与连续赋值件的不同之处
------------------------------------------------------------------------------------------
		过程赋值							|			连续赋值
	出现在initial、always语句中				|	出现在模块(module)中
											|
	过程赋值语句的执行与其周围的其他语句	|	与其他语句并行执行;在右侧操作数的值
	是有关系的								|	发生变化时执行
											|
	驱动变量								|	驱动线网
											|
	使用 = 、<=	 赋值符号					|	使用 = 赋值符号
											|
	无assign关键词							|	使用assign关键词
------------------------------------------------------------------------------------------

条件语句
	if(condition_l) begin
		procedural_statement_1
	end
	else if (condition_2) begin
		procedural_statement_2
	end
	else begin
		procedural_statement_3
	end

case 语句
	条件表达式和各分支项表达式都不必是常量表达式, 在case语句中,x、z值作为字符值进行比较。

	case语句的条件表达式和分支表达式的长度不同会出现什么情况呢?
		进行任何比较前,把case语句中所有的表达式的位宽都统一
		为这些表达式中最长的一个的位宽。

	case语句中的无关位
		case语句的两种其他形式:casez、casex,这些形式对x、z值使用了不同的解释。语法与case语句相同,关键字不同

		casez语句,在casez条件表达式和任意分支项表达式中的值为z的位都会被认为是无关位,既那个位被忽略(不进行比较)
		casex语句,值为x或z的位都会被认为是无关位。

		casez语句示例:
		casez(intr_mask)
			4'b1???:rtc_wdata[4] = 0;
			4'b01??:rtc_wdata[3] = 0;
			4'b001?:rtc_wdata[2] = 0;
			4'b0001:rtc_wdata[1] = 0;
		endcase

	字符?可以用来代替字符Z,来表示无关位。
	casez语句:若intr_mask的第1位是1(忽略intr_mask的其他位),则rtc_wdata[4] = 0;
	casez语句:若intr_mask的第1位是0,第2位是1(忽略intr_mask的其他位),则rtc_wdata[3] = 0;
	依次类推



循环语句
	有4类循环语句 forever 、repeat、while、for循环

	forever 循环语句
		循环语句格式:

		forever
			procedural_statement

		此循环语句连续执行过程性语句。因此为了跳出这样的循环,可以在过程性语句内使用中止语句。同时,在过程语句中必须使用某些方式的时序控制,否则forever循环将在0延迟后永远循环下去。
			forever循环语句的示例:
				initial begin
					clk1hz = 0;
					#5 forever
					#10 clk1hz = ~clk1hz;
				end
			上面代码生成一个时钟波形, clk1hz首先被初始化为0,并一直保持为0到第5个单位时刻。此后每隔10个时间单位,clk1hz反向一次。

	repeat 循环语句 指定循环次数
		语法格式
			repeat (loop_count)	//循环次数
				procedural_statement

		按照指定的循环次数来执行过程性语句,循环计数表达式的值为 x、z,则循环的次数按照0处理
		repeat(count)
			sum = sum + 10;

		循环语句与重复事件控制的不同

		repeat(loop_count)			//循环语句
			@(posedge clk_rtc) accum = accum + 1;
		等待clk_rtc上出现正跳变沿,然后对accum进行加1,循环执行loop_count 次

		accum = repeat(loop_count) @(posedge clk_rtc) accum + 1;	//重复事件控制
		首先计算 accum + 1,随后等待在clk_rtc上出现 loop_count 次正跳变,最后赋值。

		repeat(NUM_OF_TIMES) @(negedge zclk); //等待zclk上出现NUM_OF_TIMES下降沿,然后执行紧随在 repeat 语句之后的语句。

	while 循环语句
		格式:
		while(condition)		// condition 状态状况
			procedural_statement

		while 和C语言相似,若条件表达式位x、z。它也同样按照0(假)来处理。
		while(shift_by > 0) begin
			acc = acc << 1;
			shift_by = shift_by - 1;
		end

	for 循环语句
		for(initial_assignment;condition;step_assignment)	//初始值 条件 步骤
			procedural_statement

		for循环语句会重复执行过程性语句若干次。 与C语言相似

		integer k;

		for(k = 0; k < MAX_RANGE ; k = k +1) begin
			if(hold_data[k] == 0) begin
				hold_data[k] = 1;
			end
			else if(hold_data[k] == 1) begin
				hold_data[k] = 0;
			end
			else	begin
				$display("hold_data[k] is an x or a z")
			end
		end

	过程性连续赋值
		过程性连续赋值语句是一种能够在 always initial 语句块中出现的语句。
		这种赋值方式可以改写(Override)所有其他语句对线网或者变量赋值。
		它允许赋值语句中的表达式被连续地驱动进入到变量或线网当中去。
		过程连续赋值语句与连续赋值语句是有区别的,连续赋值语句只能出现在initial always 语句之外
			过程性连续赋值语句有两种类型(推测成对出现)
			(1) assign deassign 过程性值语句:对变量进行赋值
			(2) force release 过程性语句:虽然也可以用于对变量赋值,但主要用于对线网赋值。
			assign force语句在某种意义上是 ”连续的“: 就是当assign force 语句生效时,右式中的操作数的任何变化都会引起赋值语句的重新执行。
			过程性连续赋值的目标不能是变量的部分选择或位选择

	assign deassign 语句
		assign	过程性语句 可以改写所有的 过程性赋值语句 对变量进行的赋值。
		deassign 过程性语句 用来结束对变量的连续赋值。
		变量中的值一直保留到它被重新赋值为止。

		module d_flip_flop(d,clear,clock,q);
			input d,clear,clock;
			output reg q;

			always @(clear)
				if(!clear)
					assign q = 0;	//d对q无效
				else
					deassign q;

			always @(negedge clock) q <= d;

		endmodule

		若clear 变为0, assign过程性语句使q清零,而不考虑任何时钟沿时的情况,既clock中 d对q没有影响。
		若clear变为1 ,deassign 语句被执行;这就使得连续性赋值被取消,以后clock就能够对q产生影响。
		若assign语句应用于一个已经用assign进行赋值的变量,则先取消原来assign语句的赋值,然后再进行新的过程性连续赋值。3

		reg[3:0]load_ctr;
		...
		load_ctr = 0;
		...
		assign load_ctr = nibble^rtc_count;
		...
		assign load_ctr = 2;	//先取消前面对load_ctr的assign 赋值,然后进行新的过程性连续赋值

		...
		deassign load_ctr;	//load_ctr 一直保持值为2
		...
		assign load_ctr[2] = 1;/* 错误 :reg变量的位选择不能够作为过程性连续赋值的目标*/

		第2个assign 语句在进行下一次赋值前先使得前面的第一个assign语句无效。 在deassign语句执行后,load_ctr 的值将一直保持为2 ,直到出现另一个对该变量的过程性连续赋值。

		assign 语句在某种意义上是 ”连续性“的;既在第1个assign 语句执行后到第2个 assign语句开始执行前, nibble 、rtc_count 上的任何变化将使得第1个assign 语句被重新计算。

	force release 语句
		force release 过程性语句与 assign deassign 语句非常相识,不同之处是force release 过程语句不仅能够应用于线网,还能够应用于变量。

		当force语句应用于变量时,变量的当前值被force 语句中表达式的值覆盖;当release 语句应用于变量时,变量中的当前值保持不变,除非对它进行过程性连续赋值(在force语句被执行时),这种情况下,连续赋值为变量确立一个新值。

		当用force过程性语句对线网进行赋值时,该赋值语句将忽略线网所有的其他驱动源,直到对该线网执行release 语句。

		wire test_reset;
		...
		or #1(test_reset,penable,rtc_intr);

		initial begin
			force test_reset = penable&rtc_intr;
			#5;
			release test_reset;

		end

		force 语句的执行得 test_reset值(由penable & rtc_intr求得) ,改写来自于或门基元(原语)的值,直到执行release 语句,才恢复由或门原语输出驱动 test_reset重新生效。
		当force 语句有效的时候(前5个时间单位内),在penable 和rtc_intr上的任何变化都会使能赋值语句被重新执行。

		例:
		reg[2:0]pr_data;
		...
		pr_data = 2;
		force pr_data = 1;
		...
		release pr_data;	//pr_data 保持值1
		...
		assign pr_data = 5;
		...
		force pr_data = 3;	//当用force过程性语句对线网进行赋值时,该赋值语句将忽略线网所有的其他驱动源
		...
		release pr_data ;	//pr_data 的值变为 5
		...
		force pr_data[1:0] = 3;	/* 错误:reg变量的部分选择不能被设置为过程性连续赋值的目标*/
		
		对pr_data的第1次release 使得pr_data的值保持为1。因为在force语句生效时刻没有别的过程性连续赋值语句对该变量进行赋值。
		在执行后一个release语句后,由于对pr_data的过程性连续赋值语句又开始生效,pr_data被重新赋值为5。
		
		握手协议示例
		always 语句可以用于描述交互进程的行为,如交互式有限状态机的行为。同一个模块内的语句可以通过所有always 语句都可见的变量来进行相互通信。不建议使用在 always 内部声明 reg 变量在always 语句之间传递信息。