运算符
- 逻辑运算符:&& 与, || 或, !非,&& || 优先级高于! ,!高于算术运算符。(a>b) && (x>y) 可以写成 a>b && x>y;一般还是加上括号。
- 关系运算符,a<b a>=b 声明的关系是假的,返回0。优先级低于算术运算符。a<size-1 等于 a<(size-1) 。size -(1<a) 括号优先级最高。
- 等式运算符,== ,!=,===,!===,双目运算符,== , != 逻辑等式运算符,当操作数某些位是不定值或高阻值时,结果可能为不定值x。===,!== 对操纵数比较时对某些位的不定值x高阻值z也进行比较,两个操作数必须完全一致结果才1,否则0.。这俩常用于case 表达式判别,也叫case等式运算符。这四个优先级相同。
- 位移运算符, a>>n a<<n ,用0填补移出的空位。
module shift;
reg[3:0] start,result;
initial;
begin;
start = 1; // start初始时设为0001
result = (start <<2); // 位移后为 0100 赋值给result
end
endmodule
4'b1001 <<1 = 5'b10010 4'b1001 << 2 = 6'b100100;
1<<6 = 32'b1000000 4'b1001 >> 4 = 4'b0000 注意位移前后变量的位数
- 位拼接运算符,可以把两个或多个信号某些位拼接起来进行运算操作,{a,b[3:0],w,3’b101} ,将信号的某些位详细的列出来。也可以写成{a,b[3],b[2],b[1],b[0],w,1’b1,1’b0,1’b1} 。位拼接表达式中不允许存在没有指明位数的信号,可以使用重复法简化 {4{w} }= {w,w,w,w},嵌套表达 {b,{3{a,b}] = {b,a,b,a,b,a,b}
可以借助拼接符用一个信号名标识由多位信号组成的复杂信号。比如一个控制信号assign control = {read,write,sel[2:0],halt,load_instr};
- 缩减运算符,单目运算符,不同于双目的位运算。对单个操作数进行或与非递推运算,最后结果是1位的二进制数。过程:操作数第一位与第二位进行或,与,非运算,将结果与第三位进行或,与,非,类推到最后一位。简化程序
reg[3:0] B;
reg C;
C = &B; //相当于 C =((B[0]&B[1]) & B[2]) & B[3];
- 优先级,
- 关键词,
赋值语句
- 非阻塞赋值方式( b<=a;),所赋得变量值不能立即就为下面得的句所用。 块结束后才能完成这次赋值操作,赋得变量值是上一次赋值得到,编写可综合得时许逻辑模块时,最常用这个。注意<= 符号不是小于等于,意义不同
- 阻塞赋值方法 b=a ,赋值语句执行完,块结束,b的值在赋值语句执行完后立刻改变。
always 模块内的reg信号都采用非阻塞式,赋值不是马上执行,等always块结束后,才进行赋值。阻塞赋值看起来很直观,但可能引起麻烦(时序电路中)
always @(posedge clk)
begin
b<=a; // 定义了两个reg型信号b,c,clk上升沿到来,b=a,c=b。赋值再always块结束后执行
c<=b; // c为原来b的值,用了两个触发器
end
c为原来b的值
always @(posedge clk)
begin
b=a;
c=b; // 采用阻塞赋值方式,clk上升沿到来,b马上取a值,c马上取b值(等于a),只用了一个触发器
end
块语句
通常用来将两条或多条语句组合再一起,使其在格式上看更像一条语句。有两种一种,begin-end语句,标识顺序执行的语句,标识的块为顺序块,另一种fork-join语句,通常标识并行执行的语句,标识并行快。
- 顺序快,语句按顺序执行,一条一条执行,每条语句的延迟时间是相对于前一条语句的仿真时间而言。最后一条语句执行完,程序流程控制跳出语句块。
begin // 跟C的 {} 一样。
areg = breg;
creg = areg; // creg 值为 breg的值,这两条赋值语句之间没有延迟
end
begin
areg = breg;
#10 creg = areg; // 两条赋值语句间延迟10个时间单位
end
parameter d=50; //声明d是一个参数
reg [7:0] r; //声明r是一个8位的寄存器变量
begin //由一系列延迟产生的波形
#d r = 'h35;
#d r = 'hE2;
#d r = 'h00;
#d r = 'hF7;
#d -> end_wave; //触发事件end_wave
end
// 用顺序块和延迟控制组合产生一个时许波形
- 并行块,块内语句并行,即程序流程控制一进入到该并行块,块内语句则开始同时并行的执行。块内每条语句的延迟时间是相对于程序控制进入到块内的仿真时间的。延迟时间用来给赋值语句提供执行时序的。按时间时许排序在最后的语句执行完后或一个disable语句执行时,程序流程控制跳出该程序块。一般在 initial ,always 过程块中使用。
fork // 产生波形
# 50 r='h35;
# 100 r = 'hE2; // 进入块,延迟50个时间单位执行第一句,同时延迟100个单位较第一句延迟50执行第二句
# 150 r = 'h00;
# 200 r = 'hF7;
# 250 -> end_wave // 触发事件 end_wave
join
- 块名,在begin,fork后面,提供了在任何仿真时刻确认变量名的方法。加上块名,可以在块内定义局部变量,可以允许块被其他语句调用(如disable)。Verilog中,变量都是静态的,变量都只有唯一个地址,通过块名就可以访问该地址得到值。
- 起始时间,结束时间。并行块,顺序块中都有一个起始时间,结束时间的概念。顺序块起始时间是第一条语句开始执行的时间,结束时间最后一天语句执行完的时间。并行块起始时间所有语句是相同的,即程序流程进入该块的时间,结束时间是按时间排序在最后语句执行结束的时间。
当一个块嵌入另一个块中,起始时间结束时间就很重要。只有该块完全执行后,后面的语句才可以执行。在fork-join 块内,各条语句不用按顺序给出,各条语句前后一样。
fork
#250 -> end_wave;
#200 r='hF7;
# 150 r = 'h00;
# 100 r = 'hE2;
# 50 r='h35;
join // 跟前面的效果一样。
条件语句 if-else
根据条件判定执行操作,必须在过程块语句(always,initial语句引导的执行语句集合)中使用,begin-end块中也可以编写,模块中其他地方不能编写
- 条件中根据表达式逻辑值判定,0,x,z 假,1真
- 允许一定形式的简写:
if(cond) == if(cond==1) / if(!cond ) == if(cond != 1)
if(a>b)
out1 = int1;
else
out1 = int2;
always @( some_event) //块语句
begin
if(a>b) out1 = int1;
else if (a==b) out1 = int2;
else out1 = int3;
end
// if else后可以跟着一个内嵌的操作语句,也可以多个,多个用begin-end包含成复合语句块。
if(a>b)
begin
out1<=int1;
out2<=int2;
end
else
begin
out1<=int2;
out2<=int1;
end
// if的嵌套
if(cond1)
if(cond2) xxx;
else xxx;
else
if(cond3) xxx;
else xxx; // else与上面最接近的if配对,如果if,else数量不一样,可以用begin - end 确定配对关系
if()
begin
if() xxx;
end
else // 这时else与第一个if配对
xxx;
if(index>0)
begin
for(scani=0;scani<index;scani=scani+1)
if(memory[scani]>0)
begin
$display("...");
memory[scani]=0;
end
end
else /*WRONG*/
$display("error-indexiszero");
//一个例子,检测index变量,确定三个寄存器哪一个值与index相加作为内存的寻址地址,并将值存入寄存器index下次使用
//定义寄存器和参数。
reg [31:0] instruction, segment_area[255:0];
reg [7:0] index;
reg [5:0] modify_seg1, modify_seg2, modify_seg3;
parameter
segment1=0, inc_seg1=1,
segment2=20, inc_seg2=2,
segment3=64, inc_seg3=4,
data=128;
//检测寄存器index的值
if(index<segment2)
begin
instruction = segment_area[index + modify_seg1];
index = index + inc_seg1;
end
else if(index<segment3)
begin
instruction = segment_area[index + modify_seg2];
index = index + inc_seg2;
end
else if (index<data)
begin
instruction = segment_area[index + modify_seg3];
index = index + inc_seg3;
end
else
instruction = segment_area[index];
case 语句
多分支选择。常用于处理器的指令译码
reg [15:0] rega; // 对寄存器rega译码确定result的值
reg [9:0] result;
case(rega) // ()内是控制表达式,一般是控制信号的某些位,分支表达式根据控制信号的具体状态值确定。
16 'd0: result = 10 'b0111111111; // 分支表达式 : 语句
16 'd1: result = 10 'b1011111111;
16 'd2: result = 10 'b1101111111; // 分支表达式值不同。分项中的分支表达式必须明确
16 'd3: result = 10 'b1110111111; // 表达式值位宽必须相等。控制表达式与分支表达式逐位比较。
16 'd4: result = 10 'b1111011111;
16 'd5: result = 10 'b1111101111;
16 'd6: result = 10 'b1111110111;
16 'd7: result = 10 'b1111111011;
16 'd8: result = 10 'b1111111101;
16 'd9: result = 10 'b1111111110;
default: result =10 'bx; // 可有可无,只能有一个default项。
endcase
casez() / casex() // 处理分支表达式中存在的不定值x,高阻值z的位。casez处理不考虑高阻值的比较过程
// casex 不考虑不定值,不考虑就是在表达式比较时,不该将位的状态考虑在内,可以灵活的设置对信号的某些位进行比较。
case ( select[1:2] )
2 'b00: result = 0;
2 'b01: result = flaga;
2 'b0x,
2 'b0z: result = flaga? 'bx : 0;
2 'b10: result = flagb;
2 'bx0,
2 'bz0: result = flagb? 'bx : 0;
default: result = 'bx;
endcase
case(sig)
1 'bz: $display("signal is floating");
1 'bx: $display("signal is unknown");
default: $display("signal is %b", sig);
endcase
reg[7:0] ir; // 8位控制信号
casez(ir)
8 'b1???????: instruction1(ir); // ?高阻值= z,也就是高阻值等于任何数
8 'b01??????: instruction2(ir);
8 'b00010???: instruction3(ir);
8 'b000001??: instruction4(ir);
endcase
reg[7:0] r, mask;
mask = 8'bx0x0x0x0;
casex(r^mask) // ^ 异或
8 'b001100xx: stat1;
8 'b1100xx00: stat2;
8 'b00xx0011: stat3;
8 'bxx001100: stat4;
endcase
always块内,再给定的条件下变量没有赋值,这个变量就会保持原值,也就是说会生成一个锁存器。比如always块内使用if语句,但没有else,case语句中没有default项,这时就会出现没有预料的锁存器出现。
左边若取到了那两个项之外的值,就会默认保持q的原值,综合后的电路就会自动生成锁存器。
// 一个四选一的多路选择器
module mux4_to_1(
out,i0,i1,i2,i3,s1,s0
);
output out;
input i0,i1,i2,i3; // 端口声明四个数据输入端,两个地址输入端
input s1,s0;
reg out; // 输出为寄存器类型
// 任何输入信号改变都会引起输出信号的重新计算。
// 使输出out重新计算的所有输入信号必须写入always@(xxx) 的变量列表中,
always @(s1 or s0 or i0 or i1 or i2 or i3) begin
case({s1,s0}) // 位拼接,将两位数拼接
2'b00: out = i0;
2'b01: out = i1;
2'b10: out = i2;
2'b11: out = i3;
default: out = 1'bx;
endcase
end
endmodule
循环语句
forever语句
永久循环 用于产生周期性的波形,用来作为仿真测试信号,必须写在initial块中forever 语句 / forever begin 多条语句 end
2. repeat语句 执行固定次数的循环
// repeat及加法,移位操作构成 乘法器
parameter size=8,longsize=16;
reg [size:1] opa, opb;
reg [longsize:1] result;
begin: mult // 块名
reg [longsize:1] shift_opa, shift_opb;
shift_opa = opa;
shift_opb = opb;
result = 0;
repeat(size) // 重复size次吧
begin
if(shift_opb[1])
result = result + shift_opa;
shift_opa = shift_opa <<1; // 比如2×3=6 opa = 10 opb = 11 result = b10 + b100 = b110=6
shift_opb = shift_opb >>1;
end
end
while (condition) 语句
终止条件为condition为假
// 对rega这个8位二进制数中值为1的位进行计数
begin: count1s
reg[7:0] tempreg;
count=0;
tempreg = rega;
while(tempreg) // 这。。。
begin
if(tempreg[0]) count = count + 1;
tempreg = tempreg>>1;
end
end
for语句
for(initial_assignment; condition ; step_assignment)
跟c一样,最后的表达式再执行语句后执行
// 乘法器
parameter size = 8, longsize = 16;
reg[size:1] opa, opb;
reg[longsize:1] result;
begin:mult
integer bindex;
result=0;
for( bindex=1; bindex<=size; bindex=bindex+1 )
if(opb[bindex])
result = result + (opa<<(bindex-1));
end
/*在for语句中,循环变量增值表达式可以不必是一般的常规加法或减法表达式。
下面是对rega这个八位二进制数中值为1的位进行计数的另一种方法。:*/
begin: count1s
reg[7:0] tempreg;
count=0;
for( tempreg=rega; tempreg; tempreg=tempreg>>1 )
if(tempreg[0])
count=count+1;
end
顺序块和并行块
块语句就是将多条语句合成一组,像一条语句一样,比如begin-en那样的顺序块。
顺序块(过程块)
块中语句一条一条执行只有前面的执行完才能执行后面的(处理带内嵌延迟控制的非阻塞赋值语句)。
reg x, y;
reg [1:0] z, w;
initial // 再仿真0时刻,xyzw的值分别为 0 1 1 2,执行这四个赋值语句有顺序但不需要执行时间。
begin
x = 1'b0;
y = 1'b1;
z = {x, y};
w = {y, x};
end
//说明 2: 带延迟的顺序块
reg x, y;
reg [1:0] z, w;
initial
begin
x = 1'b0; //在仿真时刻0 完成
#5 y = 1'b1; //在仿真时刻5 完成
#10 z = {x, y}; // 在仿真时刻15 完成 ,执行有顺序
#20 w = {y, x}; // 在仿真时刻35 完成
end
并行块
由fork-join声明,并行块内语句并行执行,执行顺序由各自语句内延迟或事件控制决定。语句中的延迟或事件控制是相对于块内语句开始执行的时刻而言。
reg x, y;
reg [1:0] z, w;
initial
fork
x = 1'b0; // 在仿真时刻0 完成
#5 y = 1'b1; // 在仿真时刻5 完成
#10 z = {x, y}; // 在仿真时刻10 完成
#20 w = {y, x}; // 在仿真时刻20 完成
join
如果两条语句同时对同一个变量产生影响就会产生竞争,这种情况需要避免。实际上CPU任意时刻只能执行一条语句,但不同仿真器按照不同的顺序执行,这种竞争就无法正确的处理。
块语句的特点
- 嵌套块,顺序块和并行块可以混合使用。
//嵌套块
initial
begin
x = 1'b0;
fork
#5 y = 1'b1;
#10 z = {x, y};
join
#20 w = {y, x};
end
endmodule
- 命名块,给块命名,可以声明局部变量,命名块中声明的变量可以通过层次名引用进行访问。
//命名块
module top ;
initial
begin : block1 //名字为block1的顺序命名块
integer i ; //整型变量 i 是block1命名块的静态本地变量
//可以用层次名top.block1.i 被其他模块访问
...
end
initial
fork : block2 //名字为block2的并行命名块
reg i ; //寄存器变量 i 是block2命名块的静态本地变量
//可以用层次名top.block2.i 被其他模块访问
...
join
- 命名块的禁用,通过disable提供了一种终止命名块执行的方法。disable可以用来从循环中退出,处理错误条件以及根据控制信号来控制某些代码段是否被执行,对块语句的禁用导致紧接在后面的那条语句执行。类似于break,但disable可以禁用设计中任意一个命名块。
// 寄存器的各个位中从低有效位开始找寻第一个值为1的位
reg [15:0] flag;
integer i; //用于计数的整数
initial
begin
flag = 16'b 0010_0000_0000_0000;
i = 0;
begin: block1 //while循环声明中的主模块是命名块block1
while(i < 16)
begin
if (flag[i])
begin
$display("Encountered a TRUE bit at element number %d", i);
disable block1; // 在标志寄存器中找到了值为真(1)的位,禁用block1
end
i = i + 1;
end
end
end
生成块
动态的生成Verilog代码,方便了参数化模块的生成。当对矢量(就方括号sum[3:0])中的多个位进行重复操作时,或者当进行多个模块的实例引用的重复操作时,或者再根据参数的定义确定程序中是否应该包含某段Verilog代码的时候,生成语句可以大大简化程序的编写过程。
可以控制变量的声明,任务或函数的调用,还能对实例引用进行全面的控制(得看例子)。关键字:generate-endgenerate。当例化(实例调用吧)多个相同的模块时,一个一个的手动例化会比较繁琐。用 generate 语句进行多个模块的重复例化,可大大简化程序的编写过程。
module full_adder4(
input [3:0] a , //adder1
input [3:0] b , //adder2
input c , //input carry bit
output [3:0] so , //adding result
output co //output carry bit
);
wire [3:0] co_temp ;
//第一个例化模块一般格式有所差异,需要单独例化
full_adder1 u_adder0(
.Ai (a[0]), // 少的为左边补零
.Bi (b[0]),
.Ci (c==1'b1 ? 1'b1 : 1'b0),
.So (so[0]),
.Co (co_temp[0]));
genvar i ;
generate
for(i=1; i<=3; i=i+1) begin: adder_gen // 重复生成
full_adder1 u_adder(
.Ai (a[i]),
.Bi (b[i]),
.Ci (co_temp[i-1]), //上一个全加器的溢位是下一个的进位
.So (so[i]),
.Co (co_temp[i]));
end
endgenerate
assign co = co_temp[3] ;
endmodule
生成实例可以是以下的一个或多个类型;1.模块,2.自定义的原语,3.门级原语,4.连续赋值语句,5.initial和always块。
生成的声明和生成的实例能够再设计中被有条件的调用(实例引用),设计中可以多次调用生成的实例和生成的变量声明。生成的实例有位移的标识名用于层次命名规则引用。
循环生成语句
可以对下面的模块或模块项进行多次实例引用:1. 变量声明,2. 模块,3. 用户定义原语,门级原语,4. 连续赋值语句,5. initial 和always 块。
对两个N位总线变量进行按位异或。
//本模块生成两条N位总线变量的按位异或
module bitwise_xor ( out , i0 , i1 ) ;
//参数声明语句。参数可以重新定义
parameter N = 32 ; // 缺省的总线位宽为32位
//端口声明语句
output [ N-1 : 0 ] out ;
input [ N-1 : 0 ] i0 , i1 ;
//声明一个临时循环变量。
//该变量只用于生成块的循环计算。
//Verilog仿真时该变量在设计中并不存在
genvar j ;
//用一个单循环生成按位异或的异或门(xor)
generate
for ( j = 0 ; j < N ; j = j + 1 )
begin : xor_loop // 循环生成语句的名字
xor g1 (out [ j ] , i0 [ j ] , i1 [ j ] ) ;
end // 在生成块内部结束循环
endgenerate //结束生成块
//另外一种编写形式
//异或门可以用always块来替代
// reg [ N-1 : 0] out ;
// generate
// for ( j = 0 ; j < N ; j = j + 1 )
// begin : bit
// always @ ( i0 [ j ] or i1 [ j ] ) out [ j ] = i0 [ j ] ^ i0 [ j ] ;
// end
// endgenerate
endmodule
仿真前仿真器会对生成块中的代码进行确立(展平),将生成块转换为展开的代码,然后对展开的代码进行仿真,因此生成块本质是使用循环内的一条语句代替多条重复语句,简化编程。关键词genvar 用于生成变量,生成变量只能用于生成块中,实际中这个变量不存在的。
一个生成变量的值只能由循环生成语句来改变。循环生成语句可以嵌套使用,但使用同一个变量作为索引的循环生成语句不能互相嵌套。生成语句的名,可以通过它对循环生成语句中的变量进行层次化引用,xor_loop[0].gl ,xor_loop[31].gl 这样可以获得各个异或门的相对层次名。
重要的就是想象处循环生成语句被展平之后的形式。
//本模块生成一个门级脉动加法器
module ripple_adder ( co , sum , a0 , a1 , ci ) ;
//参数声明语句,参数可以重新定义。
parameter N = 4 ; // 缺省的总线位宽为4
//端口声明语句
output [ N-1 : 0 ] sum ;
output co ;
input [N-1 : 0 ] a0 , a1 ;
input ci ;
//本地线网声明语句
wire [N-1 : 0 ] carry ;
//指定进位变量的第0位等于进位的输入
assign carry [0] = ci ;
//声明临时循环变量。该变量只用于生成块的计算。
//由于在仿真前,循环生成已经展平,所以用Verilog对
//设计进行仿真时,该变量已经不再存在。
genvar i ;
//用一个单循环生成按位异或门等逻辑
generate for ( i = 0 ; i < N ; i = i + 1 ) begin : r_loop
wire t1 , t2 , t3 ;
xor g1 ( t1 , a0[ i ] , a1 [ i ] ) ;
xor g2 ( sum [ i ] , t1 , carry [ i ] ) ;
and g3 ( t2 , a0[ i ] , a1 [ i ] ) ;
and g4 ( t3 , t1 , carry [ i ] ) ;
or g5 (carry [ i + 1 ] , t2 , t3 ) ; // 进位
end // 生成块内部循环的结束
endgenerate //生成块的结束
// 根据上面的循环生成,Verilog编译器会自动生成以下相对层次实例名
// xor : r_loop[0].g1 , r_loop[1].g1 , r_loop[2].g1 , r_loop[3].g1 ,
// r_loop[0].g2 , r_loop[1].g2 , r_loop[2].g2 , r_loop[3].g2 ,
// and : r_loop[0].g3 , r_loop[1].g3 , r_loop[2].g3 , r_loop[3].g3 ,
// r_loop[0].g4 , r_loop[1].g4 , r_loop[2].g4 , r_loop[3].g4 ,
// or : r_loop[0].g5 , r_loop[1].g5 , r_loop[2].g5 , r_loop[3].g5
// 上面生成的实例用下面这些生成的线网连接起来
// Nets : r_loop[0].t1 , r_loop[0].t2 , r_loop[0].t3
// r_loop[1].t1 , r_loop[1].t2 , r_loop[1].t3
// r_loop[2].t1 , r_loop[2].t2 , r_loop[2].t3
// r_loop[3].t1 , r_loop[3].t2 , r_loop[3].t3
assign co = carry [ N ] ;
endmodule
条件生成语句
类似于if-else ,可以再设计模块中有条件的调用Verilog的结构,比如模块,赋值,initial,always块
// 本模块实现一个参数化乘法器
// a0_width or a1_width 小于8(条件),调用超前进位乘法器,否则调用树形乘法器。
module multiplier ( product , a0 , a1 ) ;
//参数声明,该参数可以重新定义
parameter a0_width = 8 ;
parameter a1_width = 8 ;
//本地参数声明
//本地参数不能用参数重新定义(defparam)修改 ,
//也不能在实例引用时通过传递参数语句,即 #(参数1,参数2,...)的方法修改
localparam product_width = a0_width + a1_width ;
//端口声明语句
output [ product_width - 1 : 0 ] product ;
input [ a0_width - 1 : 0 ] a0 ;
input [ a1_width - 1 : 0 ] a1 ;
//有条件地调用(实例引用)不同类型的乘法器
//根据参数a0_width 和 a1_width的值,在调用时
//引用相对应的乘法器实例。
generate
if ( a0_width < 8 ) | | ( a1_width < 8 )
cal_multiplier # ( a0_width , a1_width ) m0 ( product , a0 , a1 ) ;
else
tree_multiplier # ( a0_width , a1_width ) m0 ( product , a0 , a1 ) ;
endgenerate //生成块的结束
endmodule
case生成语句
//本模块生成N位的加法器
module adder ( co , sum , a0 , a1 , ci );
//参数声明,本参数可以重新定义
parameter N = 4 ; // 缺省的总线位宽为4
//端口声明
output [ N-1 : 0 ] sum ;
output co ;
input [ N-1 : 0 ] a0 , a1 ;
input ci ;
// 根据总线的位宽,调用(实例引用)相应的加法器
// 参数N在调用(实例引用)时可以重新定义,调用(实例引用)
// 不同位宽的加法器是根据不同的N来决定的。
generate
case ( N )
// 当N=1, 或2 时分别选用位宽为1位或2位的加法器
1 : adder_1bit adder1 ( co , sum , a0 , a1 , ci ) ; // 1位的加法器
2 : adder_2bit adder2 ( co , sum , a0 , a1 , ci ) ; // 2位的加法器
// 缺省的情况下选用位宽为N位的超前进位加法器
default : adder_cla # ( N ) adder3 ( co , sum , a0 , a1 , ci ) ;
endcase
endgenerate //生成块的结束
endmodule
课后题
- for循环赋值不需要时间,综合时认为是重复的硬件结构
- for循环的最后嵌套时钟节拍运行的信号