Verilog HDL入门(4)

Verilog 基础语言4

4.第三章内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
结构建模
verilog HDL中的结构建模风格,结构建模风格用以下3种
Gate实例引用语句
UDP实例引用语句 or and xor
module 实例应用语句

模块
在Verilog HDL种,一个模块定义了一个基本单元,格式
module module_name(port_list);
Declarations_and_Statements
endmodule
端口列表(port_list)列出了该模块与外部模块进行通信的端口。

端口
模块的端口可以被声明为输入、输出、双向端口。端口的默认类型为线网类型(即wire类型),也可以明确地把端口声明为线网类型。在声明端口后,可在模块内将输出端口再次声明为reg变量类型。无论是在线网声明还是变量声明中,线网 变量必须与端口声明中指定的位宽一致。

端口声明还可以直接放在端口列表中而不放在模块内部,这样的端口声明风格称为模块端口声明风格 模块端口列表风格(端口声明放在模块内部)。

模块端口声明风格
module (
input wire[3:0] prog_ctr,
output wire[1:0]instr_reg,
inout wire [15:0]next_addr
);

endmodule

端口在端口列表中被声明,那么就不能在模块内部重新声明。端口还可以被进一步限定为寄存器类型(reg)端口 、有符号类型(signed)端口。
module remap_ctrl(
input[31:0]rpc_addr, //默认的类型是无符号线网类型
input signed[31:0]rpc_rdata, //有符号线网类型
output reg remap,ready, //无符号寄存器类型变量
output reg signed [31:0]rpc_wdata//rpc_wdata是有符号寄存器类型变量
);

endmodule
所有端口都是用逗号隔开,共用同一个声明的多个端口

参数端口
除了可以在模块内部声明参数外,还可以用端口列表的风格来声明参数。
module module_name
#(parameter param1 = value1,param2 = value2,...,
parameter param3 = value3) //声明参数
(port_list);
...
endmodule
在参数声明中可以选择 指定参数的位宽以及符号。
module sspm
#(parameter SIZE = 6,WIDTH = 8,
parameter [WIDTH-1:0]HOLD_VALUE = 56,
parameter DUMP_FILE = "dump.rpt")
(input chip_select,read_write,
output [WIDTH-1:0]data);
...
endmodule
若每一个实例引用语句中的参数都一样,换言之,参数值不是由实例引用语句来指定的,则参数应该被声明为局部参数。
否则参数既可以在模块内部声明,也可以在参数端口中声明。

模块实例引用: 按照位置排列的连接 按照名称的连接

连接类型:标识符(变量 线网)、位选择、部分选择、上述类型的拼接、表达式(只适用于输入端口)

未连接的端口
实例引用语句中,若端口表达式的位置为空白,就将该端口指定为未连接的端口。
例子
df u0df(.q(qs),
.qb(), //输入端口
.cock(ck),
.qd() //输出端口
); //按照名称对应
df u1df(qs, ,ck,,); //按照位置对应
模块未连接输入端的值被设置为z。模块未连接的输出端只是表示该输出端口没有被使用。

不同的端口位宽
在端口和端口表达式之间存在着一种隐含的连续赋值的关系。
因此当端口和端口表达式的位宽不一致时,会进行端口匹配,采用的位宽匹配规则与连续赋值时使用的规则相同。
module child(psa,ppy);
input [5:0]psa;
output [2:0]ppy;
...
endmodule

module top;
wire [1:2] bdl; //2位
wire [2:6] mpr; //5位

child u6child(
.psa(bdl),
.ppy(mpr)
);
endmodule
模块child的实例引用语句中,存在两个隐含的连续赋值。

psa是输入端口,所以从端口表达式往端口进行赋值
assign psa = bdl;

ppy是输出端口,所以从端口往端口表达式进行赋值
assign mpr = ppy;

bdl[2]->psa[0],bdl[1]->psa[1]。psa[5] psa[4] psa[3] psa[2] 未连接,因此值为z
mpr[6]<-ppy[0],mpr[5]<-ppy[1],mpr[4]<-ppy[2]

模块参数值
一个模块在另一个模块的内部被实例引用时,较高层次的模块能够改变较低层次模块参数的参数值。用以下两种途径可以改变模块实例的参数:
1.使用定义参数语句(defparam语句)修改参数值
2.在模块实例引用中修改参数值

1.定义参数语句
定义参数语句的格式如下:
defparam hier_path1 = value1,
hier_path2 = value2,...;

在较低层次模块中的参数层次路径明可以使用如下这样一条语句清晰地矛以设置(层次路径名),
module top (tpa,tpd,tps,tpc);
input tpa,tpd;
output tps,tpc;
defparam u5ha.XOR_DELAY = 5, //名为u5ha的半加器实例中的参数XOR_DELAY
u5ha.AND_DELAY = 2; //名为u5ha的半加器实例中的参数AND_DELAY

half_adder u5ha(tpa,tpb,tps,tpc);

endmodule

module top2(tp2p,tp2q,tp2cin,tp2sum.tp2cont);
input tp2p,tp2q,tp2cin;
output tp2sum,tp2cout;

defparam
u8fa.u1ha.XOR_DELAY = 2, //在名为u8fa的全加速器实例中引用的名为u1ha的半加器实例中的参数XOR_DELAY

u8fa.u1ha.AND_DELAY = 3, //名为u8fa的全加速器实例中引用的名为u1ha的半加器实例中的参数AND_DELAY

u8fa.OR_DELAY = 3; //名为u8fa的全加器实例中的参数OR_DELAY

full_adder u8fa(tp2p,tp2q,tp2cin,tp2sum,tp2cout);

endmodule

模块实例引用中修改参数值
模块实例的本身就能指定新的参数值,模块实例引用语句可采用以下两种风格来指定实例中的参数值:
(1)按照位置来赋对应的参数值(位置关联):第1个值对应于模块中声明的第1个参数,第2个值对应于模块中声明的第2个参数,依此类推
(2)按照名称来赋对应的参数值(名称关联):参数的名称和值都被明确地标出。因此参数地顺序并不重要,从而可以按照任意顺序来传递参数。更重要的是,不是所有的参数值都必须明确的标出

下面功能一样
module top3(tp3a,tp3d,tp3s,tp3c);
input tp3a,tp3d;
output tp3s,tp3c;

half_adder #(5,2) u4ha(tp3a,tp3d,tp3s,tp3c);
//第1个值5赋给参数AND_DELAY,该参数是在模块half_adder中声明的第1个参数
//第2个值2赋给参数XOR_DELAY,该参数是在模块half_adder中声明的第2个参数
//这是通过位置来赋值

//下面是通过名称来赋参数值
half_adder #(.AND_DELAY(5),.XOR_DELAY(2)) u12ha(tp3a,tp3d,tp3s,tp3c);
endmodule

module top4(tp4p,tp4q,tp4cin,tp4sum.tp4cont);
input tp4p,tp4q,tp4cin;
output tp4sum,tp4cont;

defparam
u22fa.u1ha.XOR_DELAY = 2, //全加器实例,u22fa中引用半加器实例u1ha中的参数XOR_DELAY
u22fa.u1ha.AND_DELAY = 3; //全加器实例,u22fa中引用半加器实例u1ha中的参数AND_DELAY

full_adder #(3) u22fa(tp4p,tp4q,tp4cin,tp4sum.tp4cont);
//值3是参数OR_DELAY的新值
endmodule

按照位置来赋参数值时,模块实例引用语句中的参数值顺序必须于与参数在被引用的底层模块中被声明的顺序一致。在模块top3中的半加器实例u4ha中,AND_DELAY 设置5 ,XOR_DELAY 设置 2。
模块top3 和top4 的解释说明了用带参数值的模块实例引用语句只能将参数值向下传递一个层次(例如 OR_DELAY),但是用参数定义语句能够修改任意层次的参数值。
注意:在带参数值的模块实例引用语句中,指定参数值位置的标记符与门级实例引用语句中定义延时的位置标记符相似。

外部端口
模块定义中,端口列表举出了模块外部可见的端口
module scram_a(arb,ctrl,mem_blk,byte);
input [0:3] arb;
input ctrl;
input [8:0]mem_blk;
output [0:3]byte;
...
endmodule

arb、ctrl、mem_blk、byte 为这个模块的端口。这些端口同时也是外部端口,即在实例引用中,当采用按照名称

在模块定义的端口列表中,这两种风格可以混合使用,换言之,在模块定义中允许只有部分端口拥有外部端口名称。
如果模块端口是按位置对应关系进行连接的,则模块实例应用语句中不能使用外部端口名称。(端口名称不能与模块实例名相同)

内部端口名称不经可以是标识符,也可以是下面类型的表达式之一:
位选择;部分选择 位选择、部分选择和标识符的拼接项。
下面举例说明:
module scram_c(
arb[0:2],ctrl,
{mem_blk[0],mem_blk[1]},byte[3]
);

input [0:3] arb;
input ctrl;
input [8:0]mem_blk;
output [0:3]byte;
...
endmodule

在scram_c模块的定义中,端口列表中包括部分选择(arb[0:2])、标识符(ctrl)、拼接项({mem_blk[0],mem_blk[1]})和位选择(byte[3])。在外部端口是位选择、部分选择、拼接项的情况下,不能隐式地指定外部端口的名称。因为若这样定义模块端口的话,在模块实例引用语句中,模块端口必须按位置对应关系才能进行互连, 示例

scram_c u_scram_c(
cab[4:6],ram_ctl,mmy[1:0],tcb
);

这个实例引用语句中,端口按照位置对应关系来进行互连;因此,cab[4:6]连接到arb[0:2],ram_ctl连接到ctrl,mmy[1]连接到mem_blk[0],mmy[0]连接到mem_blk[1],tcb连接到byte[3]。arb[3] mem_blk[8:2]没有连接到任何信号,因此这些输入信号将被赋予默认值Z

当内部端口信号与外部连接所需要的表示信号的位宽不完全一致时,若想要使用按名称对应关系连接,(内部端口并不所有信号需要与外部连接时,但是又要按名称对应关系连接,采用方式)则必须为模块内部的端口明确地指定外部端口名称。
如下
module scram_d(
.data(arb[0:2]),
.control(ctrl),
.mem_word(mem_blk[0],mem_blk[1]),
.addr(byte[3])
);
input [0:3]arb;
input ctrl;
input[0:8]mem_blk;
output [0:3]byte;
...
endmodule

在模块scram_d的实例引用语句中,端口既能够按位置对应关系,也能够按名称对应关系连接,但是不能混合使用。
按名称对应关系连接的实例引用语句。
scram_d u_scram_d(
.data(cab[4:6]),.control(ram_ctrl),
.mem_word(mmy[1:0]),.addr(tcb)
);

模块也可以只有外部端口而没有相应的内部端口。例如:
module scram_e(
.data ,.control(ctrl),
.mem_word({mem_blk[0],mem_blk[1]}),.addr()
);

input ctrl;
input [8:0]mem_blk;
...
endmodule

模块scram_e有两个外部端口data、addr 没有与模块内部的任何信号相连。

一个内部端口是否能与多个外部端口相连?
Verilog HDL 允许一个内部端口与多个外部端口相连。例:
module fan_out(
.a(ctrl_in),.b(cond_out),.c(cond_out)
);
input ctrl_in;
output cond_out;

assign cond_out = ctrl_in;
endmodule

内部端口cond_out与两个外部端口b和c相连。因此在b和c上都将出现cond_out的值。

module ssp_ctrl(
.wdata({write_bus[15:12],write_bus[3:0]})
);
input [15:0]write_bus;
endmodule

module top_ssp_ctrl;
reg [7:0] ssp_select;

ssp_ctrl u_ssp_ctrl(.wdata(ssp_select));
endmodule

上面描述模块ssp_ctrl的外部端口名称是wdata,而模块top_ssp_ctrl有选择性地把ssp_select的8个位依次连接到write_bus[15:12] 和 write_bus[3:0] 上。


generate(产生,引起) 语句
允许细化时间(Elaboration - time)的选取或者某些语句的重复。这些语句可以包括模块实例引用语句、连续赋值语句、always语句、initial 语句 、门级实例引用语句等。
细化时间是指仿真开始前的一个阶段,此时所有的设计模块已经被链接到一起,并已完成层次引用。
generate 语句用关键字 generate ... endgenerate 来定界
在 generate 语句中可以出现以下 3 种语句。
(1) generate - loop 循环语句
(2) generate - case 分支语句
(3) generate - conditional 条件语句

generate 格式

generate
//generate 循环语句
//generate 条件语句
//generate 分支语句
//嵌套的 generate 语句
endgenerate

generate 循环语句
(generate 循环语句 主要功能是帮助设计者编写由多次重复所构造的复杂语句。这可借助于基于结构单元的 generatefor 循环语句,在Verilog编译细化阶段,自动地生成由结构单元实例构成的复杂代码.)

generate 循环语句被用于(Verilog 编译)细化阶段的语句复制,基本上允许对结构元素编写一个for循环。 例 普通N位的异或门
module nbit_xor
(#(parameter SIZE = 16))
(
input [SIZE-1:0] a,b,
output [SIZE-1:0] y;
);

genvar gv_i;
generate
for(gv_i = 0;gv_i<SIZE;gv_i = gv_i +1)
begin: sblka
xor uxor(y[gv_i],a[gv_i],b[gv_i]);
end
endgenerate
endmodule

在细化期间,generate 循环语句将被扩展。对于循环变量的每一个值,for循环语句的实体会被重复一次。 例子喜欢后变成
xor sblka[0].uxor(y[0],a[0],b[0]);
xor sblka[1].uxor(y[1],a[1],b[1]);
...

通常, generate 循环语句的格式如下:
for(intitial_expression;final_expression;assignment)
begin: label
statements
end
在for循环语句内,循环变量需要被初始化并且在每一次循环时都需要被修改。使用的这种循环变量被称为genvar变量。这种变量必须用genvar 声明语句来声明,并且只能够在generate 循环语句种使用。genvar 变量用于初始化语句和genvar 赋值语句中。
在模块 nbit_xor中,genvar 变量是gv_i 。
初始化时,将其赋值为0,然后每一次循环后加1。
generate 块需要标签, 这个标签用来表示 generate循环的实例名称 。在模块 nbit_xor 中,generate 块的标签是 sblka 。
实例uxor的层次路径是:
nbit_xor.sblka[0].uxor
nbit_xor.sblka[1].uxor
...

在 generate 语句中还可以有局部声明。例,如果我们在块 sblka 中包括了如下声明:

wire phy2;
那么将会出现线网变量phy2的16个实例 ,它们的层次引用名称为:
sblka[0].phy2
sblka[1].phy2
...

在细化期间,for 循环语句被扩展,即对于每一次循环,重复一次 for 语句的实体。
如果在 generate 语句内有多个语句,所有的语句都将被重复。尽管上例中的 generate 语句中只有一个门级实例引用语句,但是 generate 语句还可以包含过程性语句。
下面是一个波纹计数器的示例。
module ripple_counter
#(parameter BITS = 8)
(
input count_clk,nreset,
output[BITS-1:0]q
);

genvar gv_a;

jk_ff u0jk_ff(
.J(1'b1),.K(1'b1),.NRESET(nreset),
.CK(cond_out),.Q(q[0]),.NQ());

generate
for(gv_a = 1; gv_a < BITS; gv_a = gv_a +1'b1)
begin : gblk_a
jk_ff u1jk_ff(
.J(1'b1),.K(1'b1),.NRESET(nreset),
.CK(q[gv_a -1]),.Q(q[gv_a]),.NQ());
end
endgenerate
endmodule

genvar 变量是 gv_a 。在 generate 语句外部实例引用了一个名为 u0jk_ff 的模块实例。 在 generate 语句内部声明了另外的 BITS-1 个实例。

generate - conditional 条件语句
generate 条件语句允许在细化期间对语句进行条件选择。generate 条件语句的常见格式如下:
if(condition)
statements
[else
statements]

condition 必须是一个静态的条件,既在喜欢期间计算得出(这样一个条件表达式只能有常数和参数组成)。 statements可以是任何能够在模块出现的语句、 例如,always语句、模块实例引用语句和门级实例引用语句。
在细化期间,根据条件的值,选择相应的语句。注意:由于条件的值可能要取决于一个值从上层模块传递过来的参数,因此条件的值可能不能在细化期间被完全计算出来。
例是一个使用generate 循环语句和generate条件语句的移位寄存器。
module shift_register
#(parameter BITS = 8)
(input shift_in,clk,nreset,
output shift_out
);

wire [BITS-1:0]tq;
genvar gv_k;

generate
for(gv_k = 0 ; gv_k<BITS ; gv_k = gv_k+1'B1)
begin: gblk_x
if(gblk_x == BITS-1) begin
dflip_flop u0dff(
.d(shift_in),.nreset(nreset),.ck(clk),
.q(tq[gv_k])
);
end
else begin
if(gv_k == 0) begin
dflip_flop u1dff(
.d(tq[gv_k +1]),.nreset(nreset),.ck(clk),
.q(shift_out)
);
end
else begin
dflip_flop u2dff(
.d(tq[gv_k +1]),.nreset(nreset),.ck(clk),
.q(tq[gv_k])
);
end
end
end
endgenerate
endmodule

在细化期间,for循环语句被扩展。如果 generate 语句的变量 gv_k 的值为 BITS-1,那么就完成了移位寄存器的第一个部分(串行输入进入了触发器的 d 输入端)。
如果 generate 语句的变量 gv_k 的值为 0 , 那么就完成了移位寄存器的最后一个部分(触发器的输出端输出到 shift_out)。
当 generate 语句的变量 gv_k 取其他的所有值时,则生成了移位寄存器的中间部分(触发器的d端连接到前一个触发器的q端)
在完成细化后(假设BITS的值为8),可以得到:
dflip_flop gblk_x[7].u0dff(
.d(shift_in),.nreset(nreset),.ck(clk),
.q(tq[7])
);

dflip_flop gblk_x[6].u2dff(
.d(tq[7]),.nreset(nreset),.ck(clk),
.q(tq[6])
);

dflip_flop gblk_x[5].u2dff(
.d(tq[6]),.nreset(nreset),.ck(clk),
.q(tq[5])
);

...

dflip_flop gblk_x[0].u1dff(
.d(tq[1]),.nreset(nreset),.ck(clk),
.q(shift_out)
);

用 generate 条件形成得 generate 语句的例子
module adder
#(parameter SIZE = 4)
(input [SIZE-1 : 0]a,b,
output [SIZE-1 : 0]sum,
output carr_out
);

wire [SIZE-1 : 0] carry;
genvar gv_k;

generate
for(gv_k = 0; gv_k < SIZE; gv_k = gv_k + 1)
begin : gen_blk_adder
if(gv_k == 0) begin
half_adder u_ha(
.a(a[gv_k]),.b(b[gv_k]),.sum(sum[gv_k]),
.carr_out(carry[gv_k])
);
end
else begin
full_adder u_fa(
.a(a[gv_k]),.b(b[gv_k]),
.carry_in(carry[gv_k - 1]),.sum(sum[gv_k]),
.carry_out(carry[gv_k])
);
end
end
endgenerate
endmodule

generate - case分支语句
generate 分支语句与 generate 条件语句类似,只不过 generate 分支语句是用分支语句来进行条件性选择。 generate 分支语句的常见格式如下:
语句根据参数IMPLEMENTA TION_LEVEL,的值选择相应的加法操作行为。

module my_special_addr
#(parameter SIZE = 16,
parameter IMPLEMENTA TION_LEVEL = 0)
(input[SIZE-1:0] arg1,arg2,
output[SIZE-1:0]result
);

generate
case()
0:assign result = argl + arg2;

1:ripple_adder u_ra(.sum(result),.a(argl),.b(arg2));
2:cla u_cla(.sum(result),.a(arg1),.b(arg2));
3:fast_cla u_fcla(.sum(result),.a(argl),.b(arg2));
endcase
endgenerate
endmodule
default分支是可选的。若IMPLEMENTATION_LEVEL的值大于3,则这些分支选项中无论哪一条语句都不会被选中执行。

配置
库的概念,库是存储编译配置、UDP和模块(可以统称位cell(单元))的地方。库可以是逻辑库、符号库,包含许多已编辑好的源代码。
使用库声明语句可以声明带具体内容的库
library library_name "file_name(s)"
这条声明语句能把指定名的文件编译进名为 library_name 的逻辑库中。
库映像文件( library Map File) 是由许多上述库声明语句组成的列表。用include 语句可以在一个映象文件中包含另一个库映像文件。

include library_map_file;
下面举一个库映象文件的例子。
//文件:esoc_library:map
library lib_global "gbl/global_blocks.vg";
library lib_rtl "./*.vg";
library lib_work "/home/bond/IP/usb*.v";
include "../../global_lib_definitions";
library lib_gate "./syn/gate/*.vg";

库映像文件 esoc_library.map 包含了4个库声明语句和1个include 语句 。声明的 4 个库分别是 lib_global 、lib_rtl、lib_work 和 lib_gate。 文件 gbl/global_blocks.vg 被编译进 lib_global 。在当前文件夹中所有扩展名为*.vg 的文件都被编译进库 lib_rtl, 以次类推,在文件名中指定的路径与库映像文件按所在位置有关.

在剖析任何的源代码前,由Verilog HDL的剖析器读入库映像文件。可以拥有多个库映像文件,凡支持Verilog HDL的工具必须提供一种机制去支持读入多个多个库映象文件。

当源文件被编译时,每个源文件中的单元都被保存到库映象文件指定的相应库中去。例如,当文件usb_host.v被编译时,这个文件中所有的单元都被保存在库lib_work中。

现在回到配置这个话题,配置是指确定某实例与库中某个模块的绑定(Binding)关系