verilog语法实例学习(6)
函数和任务
函数
https://wenku.baidu.com/view/d31d1ba8dd3383c4bb4cd283.html
verilog中函数的目的是允许代码写成模块的方式而不是定义独立的模块。函数通常用于计算或描述组合逻辑。如果在模块内定义一个函数,则既可以用连续赋值语句,也可以用过程赋值语句调用。函数可以有不只一个输入,但只能有一个输出,因为函数名本身就充当输出变量。
verilog中函数还有以下几个特点:
1.函数必须在module块内调用。
2.函数内不能声明wire,所有输入输出都是局部寄存器(reg, integer)
3.函数执行完成后,才返回结果。
4.函数内不能包含任何时序控制语句。
5.函数内部可以调用其它函数,但不能调用任务。
函数的定义如下:
function [range|integer] function_name; //返回值类型和宽度,函数名
[input declaration] //输入端口说明
[parameter, reg, integer declaration] //局部变量或常量声明
begin
statement; //行为语句
end
endfunction
下面是一个16选1电路模块代码,代码中使用了函数。
//16选1电路,演示function的用法 module function1(W,S16,f); input [15:0] W; input [3:0] S16; output reg f; reg [3:0] M; function mux4to1; input [3:0] W; input [1:0] S; if(S==2'b00) mux4to1 = W[0]; else if(S==2'b01) mux4to1 = W[1]; else if(S==2'b10) mux4to1 = W[2]; else if(S==2'b11) mux4to1 = W[3]; endfunction always @(W,S16) begin M[0] = mux4to1(W[3:0],S16[1:0]); M[1] = mux4to1(W[7:4],S16[1:0]); M[2] = mux4to1(W[11:8],S16[1:0]); M[3] = mux4to1(W[15:12],S16[1:0]); f = mux4to1(M[3:0],S16[3:2]); end endmodule
`timescale 1ns/1ns `define clock_period 20 module function1_tb; reg clk; reg [15:0] W; reg [3:0] S; wire f; function1 function1_0(.W(W),.S16(S),.f(f)); always #(`clock_period/2) clk=~clk; initial begin clk = 1'b0; W = 16'b1011_1100_1001_0110; S = 4'b0000; #(`clock_period) S = 4'b0001; #(`clock_period) S = 4'b0010; #(`clock_period) S = 4'b0011; #(`clock_period) S = 4'b0100; #(`clock_period) S = 4'b0101; #(`clock_period) S = 4'b0110; #(`clock_period) S = 4'b0111; #(`clock_period) S = 4'b1000; #(`clock_period) S = 4'b1001; #(`clock_period) S = 4'b1010; #(`clock_period) S = 4'b1011; #(`clock_period) S = 4'b1100; #(`clock_period) S = 4'b1101; #(`clock_period) S = 4'b1110; #(`clock_period) S = 4'b1111; #(`clock_period) $stop; end endmodule
任务
任务具有输入输出变量,但没有返回值,更像一个模块。任务具有以下特点
1.任务只能自always(initial)块内调用。
2.任务可以具有任意个输入、输出以及双向端口(包括0个)。
3.在任务中可以使用延时、事件和时序控制结构,可以自定义时钟。
4.任务中可以调用其它函数和任务。
5.任务中不能出现过程块(always, initial)。
任务的定义;
task <任务名>;
<端口及数据类型声明语句>
<语句1>
...
<语句n>
endtask
下面是用task实现16选1模块的代码。
//16选1电路,演示task的用法 module task1(W,S16,f); input [15:0] W; input [3:0] S16; output reg f; reg [3:0] M; task mux4to1; input [3:0] W; input [1:0] S; output Result; if(S==2'b00) Result = W[0]; else if(S==2'b01) Result = W[1]; else if(S==2'b10) Result = W[2]; else if(S==2'b11) Result = W[3]; endtask always @(W,S16) begin mux4to1(W[3:0],S16[1:0],M[0]); mux4to1(W[7:4],S16[1:0],M[1]); mux4to1(W[11:8],S16[1:0],M[2]); mux4to1(W[15:12],S16[1:0],M[3]); mux4to1(M[3:0],S16[3:2],f); end endmodule
`timescale 1ns/1ns `define clock_period 20 module task1_tb; reg clk; reg [15:0] W; reg [3:0] S; wire f; task1 task1_0(.W(W),.S16(S),.f(f)); always #(`clock_period/2) clk=~clk; initial begin clk = 1'b0; W = 16'b1011_1100_1001_0110; S = 4'b0000; #(`clock_period) S = 4'b0001; #(`clock_period) S = 4'b0010; #(`clock_period) S = 4'b0011; #(`clock_period) S = 4'b0100; #(`clock_period) S = 4'b0101; #(`clock_period) S = 4'b0110; #(`clock_period) S = 4'b0111; #(`clock_period) S = 4'b1000; #(`clock_period) S = 4'b1001; #(`clock_period) S = 4'b1010; #(`clock_period) S = 4'b1011; #(`clock_period) S = 4'b1100; #(`clock_period) S = 4'b1101; #(`clock_period) S = 4'b1110; #(`clock_period) S = 4'b1111; #(`clock_period) $stop; end endmodule
函数和任务对于设计verilog代码不重要,但可以大大方便模块化设计。使用函数和任务的一个好处是它们可以从一个always块中调用,而这些块中不允许包含例化语句。随着代码量的增多,verilog的这些特点显得尤其重要。
系统函数和任务总结
为了仿真控制和对仿真结果进行比较,verilog中提供了大量的系统功能调用。这些调用分为两类:系统任务和系统函数。
verilog中系统函数和系统任务都是以$开头的标识符。一般在initial和always语句中调用系统函数和系统任务。
系统函数和系统任务的主要区别:
系统任务可以没有返回值或者有多个返回值,系统函数只有一个返回值;系统任务可以带延迟,而系统函数不可以,在0时刻立即执行。
依照功能不同,可以分为以下几类:
显示任务
$display 和 $write 任务
格式:
$display (<format>,signal0,signal1,…);
$write ((<format>,signal0,signal0,…);
这两个系统任务作用是用来输出信息,format是双引号括起来的字符串,用来定义后面要显示信号的显示格式,后面是逗号隔开的若干个信号。这两个任务语法和c语言的printf函数很相似。
这两个系统任务的区别:$display自动的在输出后进行换行,而$write不会自动换行。
format可以包含以下两种信息:
1)格式说明,由“%”和格式字符组成。它的作用是将输出的数据转换成指定的格式输出。格式说明总是从%开始的。常见的几种输出格式
输出格式 | 说明 | 输出格式 | 说明 |
%h或%H | 以十六进制形式输出 | %d或%D | 以十进制形式输出 |
%o或%O | 以八进制形式输出 | %b或%B | 以二进制形式输出 |
%c或%C | 以ASCII码形式输出 | %v或%V | 输出网格型数据信号轻度 |
%m或%M | 输出等级层次名字 | %s或%S | 以字符串的形式输出 |
%e或%E | 以指数形式输出实型数 | %f或%F | 以十进制的形式输出实型数 |
%g或%G |
以指数或者十进制数输出实型数,但是 无论何种格式都以较短的结果输出 |
%t或%T | 输出当前的时间格式 |
2)普通字符,即需要原样输出的字符。其中一些特殊的字符可以通过转换序列来输出:
换码序列 | 功能 | 换码序列 | 功能 |
\n | 换行 | \'' | 双引号字符" |
\t | 横向跳格 | \o | 1-3位八进制数代表的字符 |
\\ | 反斜杠字符 | %% | 百分符号% |
下面是代码示例:
`timescale 1ns/1ns `define clock_period 20 module vdisplay_tb; reg [7:0] a; reg [31:0] b; reg [15:0] c; initial begin a = 8'b00101010; b = 32'h12345678; c = 16'hf01f; #(`clock_period); //normal char $display("hello word,"); $display(" gkd"); $write("hello word,"); $write(" gkd"); //display simulation time $display("\nsim time is %d",$time); $display("hex: a=%h, oct: a=%o, bin: a=%b",a, a, a); $display("hex: a=%h, oct: a=%o, bin: a=%0b",a, a, a);//not show high bits 0 $display("\\\t%%\n\"\123\""); #(`clock_period); $finish; end endmodule
Compiler version M-2017.03-SP2-11; Runtime version M-2017.03-SP2-11; Jan 3 10:25 2019
hello word,
gkd
hello word, gkd
sim time is 20
hex: a=2a, oct: a=052, bin: a=00101010
hex: a=2a, oct: a=052, bin: a=101010
\ %
"S"
$finish called from file "vdisplay.v", line 27.
如果在输出列表中表达式的值包含不确定的值或高阻值,其结果遵循以下规则:
输出为十进制的情况下:
a)如果表达式值的所有位均为不定值,则输出结果为小写的x;
b)如果表达式中所有的位均为高阻值,则输出结果为小写的z;
c)如果表达式值的部分位为不定值,则输出结果为大写的X;
d)如果表达式值的部分为高阻值,则输出的结果位大写的Z;
输出为十六进制和八进制的情况下:
a)每4位二进制数为一组代表一位十六进制数,每3位二进制数为一组代表一位八进制数。
b)如果表达式值相对应的某进制的所有位均为不定值,则输出为小写的x;
c)如果表达式值相对应的某进制数的所有位均为高阻值,则输出小写的z;
d)如果表达式值相对应的某进制数的部分位为不定值,则该位输出的结果为大写的X;
e)如果表达式值相对性的某进制数的部分位位高阻值,则该位输出的结果为大写的Z;
3)对于二进制的情况:表达式的值每一位的输出结果都用0,1,x,z表示。
verilog中还有几种标准输出格式,用来输出固定格式:
$displayb/$writeb //输出2进制格式
$displayo/$writeo //输出8进制格式
$displayh/$writeh //输出16进制格式
%m用来输出module的层次结构,比如下面的代码:
`timescale 1ns/1ns `define clock_period 20 module test; initial $display("displaying in %m"); endmodule module vtop_tb; test test1(); test test2(); test test3(); initial begin #(`clock_period); $finish; end endmodule
Compiler version M-2017.03-SP2-11; Runtime version M-2017.03-SP2-11; Jan 3 10:40 2019
displaying in vtop_tb.test1
displaying in vtop_tb.test2
displaying in vtop_tb.test3
$finish called from file "vtop.v", line 16.
探测任务
$strobe属于探测监控任务,用来在某时刻所有的事件都执行完毕后,在时间步的末尾将数据输出。更多用来显示用非阻塞赋值的变量的值。
探测任务和显示任务的区别是,显示任务遇到语句开始执行,而探测任务要等到时间步的末尾才执行。
代码示例:
`timescale 1ns/1ns `define clock_period 20 module vtrobe_tb; reg [7:0] a; reg [7:0] b; initial begin a = 8'b01010101; $strobe("strobe,a = %b",a); $display("display,a = %b",a); a = 8'b11110000; #(`clock_period*2); $finish; end initial begin b <= 8'h11; $strobe("strobe,b = %b",b); $display("display1,b = %b",b); #(`clock_period); $strobe("strobe,b = %b",b); $display("display2,b = %b",b); b <= 8'h22; end endmodule
注意以下的输出顺序:
Compiler version M-2017.03-SP2-11; Runtime version M-2017.03-SP2-11; Jan 3 10:54 2019display,a = 01010101
display1,b = xxxxxxxx
strobe,b = 00010001
strobe,a = 11110000
display2,b = 00010001
strobe,b = 00100010
$finish called from file "vstrobe.v", line 15
监控任务
$monitor, 一旦调用后,将随时对输出变量名表项中列出的各个变量进行监控,如果其中任何一个发生变化,就会启动$monitor任务,在时间步结束时候,按既定的格式输出所有变量。如果在同一时刻,多个变量或表达式发生变化,则该时刻只输出显示一次。可以通过$monitoron,$monitoroff打开和关闭监控任务。在多模块调试时,可以有多个模块调用$monitor,但任意时刻只有一个monitor被启动。缺省状态下,监控任务是打开的。
代码示例:
`timescale 1ns/1ns `define clock_period 20 module vmonitor_tb; integer a, b; initial begin a = 2; b = 4; forever begin #(`clock_period) a = a + b; #(`clock_period) b = a - 1; end end initial begin $monitor($time," a=%d,b=%d",a,b); end initial begin #(`clock_period*10) $finish; end endmodule
Compiler version M-2017.03-SP2-11; Runtime version M-2017.03-SP2-11; Jan 3 13:22 2019
0 a= 2,b= 4
20 a= 6,b= 4
40 a= 6,b= 5
60 a= 11,b= 5
80 a= 11,b= 10
100 a= 21,b= 10
120 a= 21,b= 20
140 a= 41,b= 20
160 a= 41,b= 40
180 a= 81,b= 40
$finish called from file "vmonitor.v", line 23.
控制类系统任务
格式:$finish;$finish(n);
系统任务$finish的作用是退出仿真器,返回主操作系统,也就是结束仿真过程。根据参数的值输出不同的特征信息。如果不带参数,默认其值为1;
格式:$stop;$stop(n);
$stop任务的作用是将EDA工具设置为暂停模式,在仿真环境下给出一个交互式的命令,将控制权交给用户。
n的取值如下:
0 不输出任何信息。
1 输出当前的仿真时间和模拟文件的位置;
2 输出当前的仿真时间、模拟文件位置和仿真过程中所用memory和CPU的时间统计。
代码例子:
`timescale 1ns/1ns `define clock_period 20 module vfinish_tb; initial begin #(`clock_period*10) $display("time = %d",$time); $stop; #(`clock_period*10) $finish; end endmodule
比如上面代码用vcs编译后,运行simv,程序将暂停在ucli界面,我们输入quit,则退出simulation。
Compiler version M-2017.03-SP2-11; Runtime version M-2017.03-SP2-11; Jan 3 13:44 2019
time = 200
$stop at time 200 Scope: vfinish_tb File: vfinish.v Line: 9
ucli%
ucli% quit
V C S S i m u l a t i o n R e p o r t
Time: 200 ns
CPU Time: 0.560 seconds; Data structure size: 0.0Mb
Thu Jan 3 13:44:57 2019
仿真时间
在Verilog HDL中有两类型的时间系统函数,$time和$realtime。用这两个系统函数可以得到当前的仿真时间。
$time可以返回一个64位的整数来表示当前仿真时刻值,该时刻是以模块的仿真时间尺度为基准的。
$realtime返回的时间数字是一个实型数。该数也是以时间尺度为基准的。
`timescale 1ns/1ns `define clock_period 20 module vtime_tb; integer i; initial begin $monitor("time = %d,%f, i=%d",$time,$realtime,i); #(2.6) i = 0; #(3.2) i = 1; #(4.6) i = 3; #(1) $finish; end endmodule
Compiler version M-2017.03-SP2-11; Runtime version M-2017.03-SP2-11; Jan 3 14:07 2019
time = 0,0.000000, i= x
time = 3,3.000000, i= 0
time = 6,6.000000, i= 1
time = 11,11.000000, i= 3
$finish called from file "vtime.v", line 16.
修改`timescale 为 1ns/100ps,则realtime时间单位可以精确到0.1ns。
Compiler version M-2017.03-SP2-11; Runtime version M-2017.03-SP2-11; Jan 3 14:10 2019
time = 0,0.000000, i= x
time = 3,2.600000, i= 0
time = 6,5.800000, i= 1
time = 10,10.400000, i= 3
$finish called from file "vtime.v", line 16.
系统任务$random(随机函数)
这个系统函数提供了一个产生随机数的手段。当函数被调用时返回一个32位的随机数。这是一个带符号的整型数。
$random的一般用法是:$random%b,其中b>0.他给出了一个范围在(-b+1):(b-1)中的随机数。下面给出例子:
reg [23:0]rand;
rand=$random%60; //生成-59~59之间的随机数
rand={$random}%60; //生成0~59之间的随机数
通常我们在testbench当中,用$random来产生随机激励。
文件输入输出任务
1)打开文件:
文件可以用系统任务$fopen打开,用法:
文件句柄=$fopen("文件名",mode);//mode可以省略
任务$fopen返回一个被称作多通道描述符(multichannel descriptor)的32位值。mode为w/w+/a/a+。
w:打开文件并从文件头开始写,文件不存在,则创建文件。
w+:打开文件并从文件头开始读写,文件不存在,则创建文件。
a:打开文件并从文件尾开始写,文件不存在,则创建文件。
a+:打开文件并从文件尾开始读写,文件不存在,则创建文件。
2)写文件:
监控,显示,写入,探测任务都有相应的写文件版本,$fmonitor,$fdisplay、$fwrite,$strobe等。用法:
$fdisplay(文件描述符,p1,p2,..pn)
$fmonitor(文件描述符,p1,p2,...pn)
p1,p2,…pn可以是变量,信号名或者带引号的字符串。文件描述符是一个多通道描述符,他可以是一个文件句柄或者多个文件句柄的按位组合。Verilog会把输出写到与文件描述符中值为1 的文件中。
3)关闭文件
文件可以用系统任务$fclose来关闭。用法:
$fclose(文件描述符); 如:$fclose(handle1);文件一旦被关闭,多通道描述符中的相应位被设置为0,下一次的fopen的调用可以重用这一位。
系统任务$readmemb $readmemh
在Verilog中有两个系统任务$readmemb和$readmemh,并用来从文件中读取数据到存储器中。这两个系统任务可以在仿真的任何时刻都被执行使用。
(1)$readmemb("<数据文件名>",<存储器名>);
(2)$readmemb("<数据文件名>",<存储器名>,<起始地址>);
(3)$readmemb("<数据文件名>",<存储器名>,<起始地址>,<结束地址>);
(4)$readmemh("<数据文件名>",<存储器名>);
(5)$readmemh("<数据文件名>",<存储器名>,<起始地址>);
(6)$readmemh("<数据文件名>",<存储器名>,<起始地址>,<结束地址>);
在这两个系统函数中,被读取的数据文件的内容只能包括:空白字符(空格换行和制表格) 注释行(//和/**/) 二进制和十六进制数字。当地址出现在数据文件中时,其格式为@FF...F。
在上面6种系统任务格式,需补充说明一下几点:
(1)如果系统任务和数据文件都没有说明地址。则从默认的存储器地址开始写入数据,直至数据写完或者存储器存满。
(2)如果系统任务说明了起始地址,没有说明结束地址,则数据从起始地址开始存放,直至存储器的结束地址为止。
(3)如果系统任务说明了起始地址和结束地址。那么久按章任务说明中的地址进行存储,不考虑存储器的默认起始地址。
(4)如果系统任务和数据文件都说明了地址。那么数据文件中的地址说明必须包含在任务地址说明语句中,否则将出现错误信息,并且停止存储。
(5)如果数据文件中的数据个数和系统任务中起始地址和结束地址暗示的数据个数不同,也会报错。
verilog文件读写的例子参照:
https://www.cnblogs.com/mikewolf2002/p/10158575.html
值变存储文件
$dumpfile(“dump.vcd”); //把信号写入vcd格式波形文件dump.vcd
$dumpvars; //没有参数dump所有信号
$dumpvars(level,start_module); //要记录的信号,level=0表示记录所有信号
$dumpflush; //将VCD数据保存到磁盘
$dumpoff; //停止记录
$dumpon; //重新开始记录
$dumplimit(size); //限制VCD文件的大小(以字节为单位)
$dumpall; //记录所有指定的信号值
下面的示例代码,将会把所有的信号dump到dump.vcd文件中。
module adder4(cout, sum, ina, inb, cin,clk); output [3:0] sum; output cout; input [3:0] ina, inb; input cin,clk; reg[3:0] tempa, tempb, sum; reg cout; reg tempc; always @(posedge clk) begin tempa = ina; tempb = inb; tempc = cin; end always @(posedge clk) begin {cout, sum} = tempa+ tempb + tempc; end endmodule
`timescale 1ns/10ps `include "adder4.v" module adder_tp; reg[3:0] ina,inb; reg cin; reg clk = 0; wire[3:0] sum; wire cout; always #10 clk =~ clk; initial begin ina=0; repeat(20) #20 ina = $random; end initial begin inb=0; repeat(10) #40 inb = $random; end initial begin cin=0; repeat(2) #200 cin = {$random} % 16; end adder4 adder_te( .clk(clk), .sum(sum), .cout(cout), .ina(ina), .inb(inb), .cin(cin) ); initial begin $monitor($time,,,"%b + %b + %b = {%b,%b}", ina, inb, cin,cout,sum); #400 $finish; end initial begin $dumpfile("dump.vcd"); $dumpvars; end endmodule
编译指令
Verilog HDL语言和C语言一样也提供编译预处理的功能。在Verilog中为了和一般的语句相区别,这些预处理语句以符号"`"开头,注意,这个字符位于主键盘的左上角,其对应的上键盘字符为"~",这个符号并不是单引号"'".这里简单介绍最常用的`define `include `timescale.
1)宏定义`define
用一个指定的标识符(名字)来代表一个字符串,其的一般形式为: `define 标识符(宏名) 字符串(宏内容) 如:`define SIGNAL string
其作用是在后面程序中用SIGNAL替代所有的string字符串,在编译预处理时,将程序中该命令后面所有的SIGNAL替换为string。这种替代过程称作宏展开。
说明:
a)宏名可以是大写字母,也可以是小写字母。一般用大写字母,防止与后面的变量名重复。
b)`define可以出现在模块定义里面,也可以出现在外边。其有效范围是从该命令行开始至源文件结束。
c)在引用已定义的宏名时,必须在宏名的前面加上符号`,表示该名字是一个经过宏定义的名字。
d)宏定义是用宏名代替一个字符串,只做简单替换不检查语法。
e)宏定义不是Verilog HDL语句,不必在后面加分号。
f)在进行宏定义时,可以引用已经定义的宏名,可以层层替换。
g)宏名和宏内容必须在同一行进行声明。如果在宏内容中包含有注释行,注释行不会作为被置换的内容。
注意:组成宏内容的字符串不能够被以下的语句记号分隔开。注释行+数字+字符串+确认符+关键词+双目或三目运算符
如下面的宏定义声明和引用就是非法的:
`define first_half "start of string
$display(`first_half end of string")
2)文件包含处理`include
所谓文件包含是指处理一个源文件可以将另一个源文件的全部内容包含进来,即将另外文件包含到本文件之中。一般格式为: `include"文件名"
在执行命令时,将被包含文件的全部内容复制插入到`include命令出现的地方,然后继续进行下一步的编译。关于文件包含的几点说明:
1)一个文件包含命令只能制定一个被包含的文件,如果需要包含n个文件,要用n个`include命令。
2)`include命令可以出现在Verilog程序的任何位置。被包含文件名可以是相对路径名,也可以是绝对路径名。
3)可以将多个包含命令卸载同一行,可以出现空格和注释行。
4)如果文件1 包含文件2,文件2需要用到文件3的内容,可以在文件一种用两个`include命令分别将文件2和文件3包含进去,而且文件3要在文件2之前。
5)在一个被包含文件中又可以包含其他的文件,即文件的包含是可以嵌套的。
3)时间尺度`timescale
`timescale命令用来说明跟在该命令后面的模块的时间单位和精度。使用`timescale命令可以在同一个设计中包含不同的时间单位的模块。一般的命令格式如下:`timescale<时间单位>/<时间精度>
在这条命令中,时间单位参量是用来定义模块中的仿真时间和延迟时间的基准单位的。时间精度是用来声明该模块的仿真时间的精确程度的,该参量被用来对延迟时间值进行取证操作,因此又可以称作是取整精度。如果在同一个程序设计里,存在多个`timescale一样的命令,则用最小的时间精度值来决定仿真的时间单位。另外时间精度不能大于时间单位值。
使用`timescale时应该注意,`timescale的有效区域为`timescale语句处直至下一个`timescale命令或者`resetall语句为止。当有多个`timescale命令时,只有最后一个才起作用,多以在同一个源文件中`timescale定义的不同的多个模块最好分开编译,不要包含在一起以免出错。
`timescale 1ns/1ps //时间值都为1ns的整数倍,时间精度为1ps,因此延迟时间可以表达为带三位小数的实型数。
`timescale 10μs/100ns //时间单位为10μs的整数倍,时间精度位100ns,因此延迟时间可以表达为带两位小数的实型数。
时间尺度
1)根据时间精度,参数p的值从1.55取整为1.6;
2)因为时间单位是10ns,时间精度为1ns,所以延迟时间#p作为事件单位的整数倍为16ns;
3)可以用$printtimescale函数来输出显示一个模块的时间单位和时间精度。
4)条件编译命令`ifdef `else `endif
一般情况下,Verilog HDL源程序中所有的航都参加编译。但是有时希望对其中的部分内容只有在满足编译条件时才进行编译。也就是对一部分内容指定编译条件,即条件编译。
条件编译命令有以下几种形式:
`ifdef 宏名 (标识符)
程序段1
`else
程序段2
`endif
它的作用是当宏名已经被定义过(`define定义),则对程序1进行编译,程序段2被忽略。其中else部分可以没有。注意:忽略掉的程序段也要符合语法规则