微波EDA网,见证研发工程师的成长!
首页 > 研发问答 > 嵌入式设计讨论 > FPGA,CPLD和ASIC > 夏老师的书,学习笔记,按章更新 咳咳,上路。自己码的哦

夏老师的书,学习笔记,按章更新 咳咳,上路。自己码的哦

时间:10-02 整理:3721RD 点击:

第二章学习心得

1 未定义的输出端口是wire型的

2 如果是模块之间的连接线,有进,有出,则不用定义这样的wire 型变量。

3 wire型变量进行操作时需要使用assign操作符,assign不能出现在always中。

4 对于模块,时间延时#写在模块之前,模块定义名之后。

5对于三态门可以用bufif1来实例化,比较简单的功能,

6 reg类型不赋值,默认是x;wire类型不赋值,默认是高阻z。

7 BUFT的真值表是:T输入为1时,不论I输入为什么,输出为Z

                   T输入为0时,I输入什么,输出即为什么。

8 测试模块中,输入要用reg型,输出要用wire型,测试模块没有输入输出变量。

9 如果在测试程序中没有标明延时单位,那么延时是以时钟周期为单位的,即ns,另外,在取反操作中~和!可以达到一样的效果。

10 测试模块中initial和always是不冲突的,在这两种模块中可以对同一变量赋值

第三章学习心得

1 模块引用有两种方法,按照顺序直接书写,用.符号。

2 端口I/O说明时有inputoutput inout三种类型,I/O说明也可以在端口声明语句里,格式如下,module module_name(input port1,input port2,output port3,outputport4,inout port5);

3 端口可以声明为wirereg类型两种

4 逻辑功能实现方法有三种,assign(组合逻辑)、实例元件、always(组合时序)。

5 always中的逻辑是按照顺序执行的,begin  end之间的语句是并发的。

6 过程块(initial,always)、连续赋值语句assign、实例引用都是并行的。

7 三者之间的先后次序没有关系。

8 只有assign与实例引用可以独立于always而存在(*)

9 数据类型中共有十八种,其中,最常用的四种为reg,wire,integer,parameter四种,其他的十四种(都没见过啊)large ,medium,scalared,time ,small,tri,trio,tri1,triand,trior,trireg,vectored,wand,wor。后十四种除time外都是用于基本逻辑单元建库用的(门级和开关级的),和系统设计没关系(长出一口老气)

10 整数表达方式有四种,b,d,h,o大小写都可。

11 表达方式有仨,位宽`进制 数字,可以省略位宽,那么默认位宽起码32位,也可以只留数字,那么数字进制为:十进制。

12 z和x的用法,x和z在十六进制中代表四位二进制数,八进制中代表三位二进制数,二进制中代表一位,case中也可以用?代表z,十进制中只能用于表达整个为高阻或未知。

13 负数为在整数的最前面,即,位宽的前面加个-即可

14 下划线可以分隔数字

15 字符串一个字符是八位“ab”,是十六位。

16 parameter只能定义常量,或者由parameter定义的符号的一些简单的运算而得到的常量的重新定义。经常出现在测试模块中的时钟周期长度,时延等参数的定义。

17可以通过参数传递改变在被引用模块中的实例中已经定义的参数。(很好用的功能)

18 在一个模块中想改变另一个模块的常量参数时,需要使用defparam

19 parameter定义的变量可以实例化以后可以给其他变量赋值。例如ceshi2=T.B2.P;

20 wire 必须有连续赋值语句assign或者实例进行驱动,不然,就是z

21 reg和触发器相当,默认初始值为x

22 always中赋值的变量必须为reg型的。

23 reg型数据可以是负值,但当作为操作数时,做正值处理,即无符号数,例如,-1`b1为15(这一点还是很好理解的,实际上就是当成无符号数进行处理就好了,脑容量不够)

24 存储器,例如ram,rom和reg文件是通过对reg型变量建立数组来对存储器建模的。

26 建立的方法:reg[wordsize-1:0]name[memsize-1:0];

27 地址索引可以是表达式,及reg型变量。

27 除法运算,结果只取整数部分,取余运算,结果符号位与第一个操作数相同(没有想过这个问题)


28 按位运算符,当按位操作时,若两个数据位数不同,则按右端对齐,将较短的数自动在左端补零。



15.1.6更新如下:

第四章 运算符、赋值语句和结构说明语句
1 逻辑运算符,是为了判断真假,但是关系运算符是为了计算真假,返回值为1和0,可以参与运算,在关系运算中,有一个参数未知数时,则,结果也为未知数。
2 等式运算符中有==   != 和===  !==四种形式,主要的区别是,前两种出现xz时,结果为x,即结果也为不确定值,但是后两种,可以对x,z进行对比。后两种常用与case语句(完全没有用过啊)
3 移位运算符,空出来的位置用0进行填补。
4 移位运算要注意左移位和右移位的区别,这其中要注意,左移位会扩充位数,右移位不会扩充位数。
5 拼接运算符中可以用重复发,即:{4{w}},还可以用嵌套{a,{4{a,b}}}
6 缩减运算符,不常见,也是进行与或运算,只不过是从低位到高位依次进行,最后就剩下一位数,为单目操作符。
7 阻塞和非阻塞(这个讲太多,学过一段时间大家都明白了,只要在时序逻辑中不要用=就好了,感觉也没有必要用撒)
8 begin end中间的语句是按顺序执行的,并且可以有名字,还可以声明变量,变量类型,可以是reg,integer,parameter,real。另外,按顺序执行这点还要和并行执行的问题区分开,实际上就是并行执行的,但是按顺序来的,只是,没有延迟的按顺序执行。(已经晕了);
9 fork join并行块,这个只是相对于时延操作来说的,即#操作,不可综合,执行完,或者遇到disable即跳出。还可以在块内声明变量,比begin end 多time 和event。
10 只有块有名字,才可以定义变量,并且变量是静态的,只可以在快内使用。块有了名字就可以被其他语句用,如disable,并且,可以随时确认快内变量的值(没见过这种操作,学渣就是学渣);

第五章 条件语句 循环语句 块语句 与 生成语句

1 if else 这种语句只能出现在过程块中,即always和initial中里,必然会出现在begin end中间了。

2 if else 中判断条件中,真的唯一条件是1,其他(0,x,z)全部为假。

3 case语句有三种分别为casecasez casex,注意表达式的值和分支的值必须位宽一样,这里要特别注意case,casez,casex真值表之间的区别,case必须一模一样,包括xz,casez则忽略z位的对比,casex则忽略xz的对比,忽略位的对比结果默认为1,即,

Don’t carecondition,在比较过程中最好不要出现xz的比较,因为,这种情况是不可综合的。所以,casez和casex有些软件是不可综合的。

4 高祖情况可以用z也可以用?代替,同时要注意一位代表几bit。

5 case和if else 不要出现变量不赋值情况,若出现,则会出现锁存器。

6 case和if else 都可以嵌套使用。

7 循环语句,有的是可以综合的,但是没有特定次数的循环肯定是不可综合的。

8 forever 只能出现在initial里,不可以单独出现。不可综合。

9 repeat(size)可综合。

10 while 可综合。

11 for是可综合的。和while,repeat差不多,但用起来很方便

12 integer作为一个有符号整形数,开销比较大,打底32位,建议应用在loop和for中,其他地方不建议使用。

13 fork join 是不可综合的,可以用在initial里,切不可以同时对一个变量赋值,会引起竞争,获得的值将不确定。

14 fork join可以嵌套到beginend里用,也可以反过来,即嵌套起来使用。

15 块只有命名才能定义局部变量,同时,外部,可以访问这些变量,并且,可以利用disable来禁用命名块,例如disable block;则block块不再执行。

16 生成快的作用,generate-endgenerate作用包括重复操作与根据参数的定义决定程序中是否包含某一段代码。是verilog-2001中新定义的。

17 生成快只有三种,循环生成快,条件生成块,case生成块。

18 genvar声明的变量只能在生成块总用,且为无符号整数与integer类似,但是,其实,仿真时该变量并不存在。

19 生成块中要是有begin end,必须有名字,且就算有一句程序,也要加上begin end。

20 生成块实际上使用一条循环语句代替多重复的verilog语句,简化用户编程,仿真时,就展开了,所以,定义的genvar变量仿真时就不存在了,并且,只能在循环生成语句中改变(这个原因还是很明显的)。

21生成块可以嵌套使用。其中,always,initial,assign,reg ,wire等都可以使用。

21 关于循环生成块的例子:

Implementtation部分:

module bitwise_xor(out,i0,i1);

parameter N=32;

output reg [N-1:0] out;

input [N-1:0] i0,i1;


genvar j;


generate

for(j=0;j y)                 //给出任务定义的描述语句
      tmp = x;
  else
    tmp = y;

end

endtask
上述代码定义了一个名为“task_demo”的任务,求取两个数的最大值。在定义任务时,
有下列六点需要注意:
(1)在第一行“task”语句中不能列出端口名称;
(2)任务的输入、输出端口和双向端口数量不受限制,甚至可以没有输入、输出以及
双向端口。
(3)在任务定义的描述语句中,可以使用出现不可综合操作符合语句(使用最为频繁
的就是延迟控制语句) ,但这样会造成该任务不可综合。
(4)在任务中可以调用其他的任务或函数,也可以调用自身。
(5)在任务定义结构内不能出现 initial和 always过程块。
(6)在任务定义中可以出现“disable 中止语句” ,将中断正在执行的任务,但其是不
可综合的。当任务被中断后,程序流程将返回到调用任务的地方继续向下执行。

2.任务调用
虽然任务中不能出现 initial 语句和 always 语句语句, 但任务调用语句可以在 initial 语句
和 always 语句中使用,其语法形式如下:
task_id[(端口1,  端口 2, .,  端口 N)];
其中 task_id是要调用的任务名,端口 1、端口 2,…是参数列表。参数列表给出传入任
务的数据(进入任务的输入端)和接收返回结果的变量(从任务的输出端接收返回结果) 。
任务调用语句中,参数列表的顺序必须与任务定义中的端口声明顺序相同。任务调用语句是
过程性语句,所以任务调用中接收返回数据的变量必须是寄存器类型。下面给出一个任务调
用实例。

例:通过 Verilog HDL 的任务调用实现一个 4 比特全加器。

module EXAMPLE (A, B, CIN,S, COUT);

input [3:0] A, B;
input CIN;
output [3:0] S;
output COUT;

reg [3:0] S;
reg COUT;
reg [1:0] S0, S1, S2, S3;

task ADD;

input A, B, CIN;
output [1:0] C;

reg [1:0] C;
reg S, COUT;

begin

S = A ^ B ^ CIN;
COUT = (A&B) | (A&CIN) | (B&CIN);
C = {COUT, S};
end
endtask

always @(A or B or CIN) begin
ADD (A[0], B[0], CIN, S0);
ADD (A[1], B[1], S0[1], S1);
ADD (A[2], B[2], S1[1], S2);
ADD (A[3], B[3], S2[1], S3);
S = {S3[0], S2[0], S1[0], S0[0]};
COUT = S3[1];
end
endmodule

在调用任务时,需要注意以下几点:
(1)任务调用语句只能出现在过程块内;
(2)任务调用语句和一条普通的行为描述语句的处理方法一致;
(3)当被调用输入、输出或双向端口时,任务调用语句必须包含端口名列表,且信号
端口顺序和类型必须和任务定义结构中的顺序和类型一致。需要说明的是,任务的输出端口
必须和寄存器类型的数据变量对应。
(4)可综合任务只能实现组合逻辑,也就是说调用可综合任务的时间为“0” 。而在面
向仿真的任务中可以带有时序控制,如时延,因此面向仿真的任务的调用时间不为“0” 。


10 task也可以被generate 调用。验证程序如下所示:

modulebitwise_xor(out,i0,i1);

parameterN=32;

outputreg [N-1:0] out;

input[N-1:0] i0,i1;


genvarj;


generate

for(j=0;j 事件名,不可综合,仿真的时候还是很好用的,例程如下所示:

测试文件:

`timescale 1 ns/ 1 ps

module hardreg2_top;

reg clock,clearb;

reg[3:0] data;

wire [3:0] qout;

`define stim #100 data=4'b

event end_fist;

hardreg2reg_4bit(.d(data),.clk(clock),.clrb(clearb),.q(qout));

initial

begin

clock =0 ;

clearb =0;

end

always # 50 clock =~clock;

always @(end_fist)

clearb=~clearb;

always @(posedge clock)

$display("attime %d clearb= %b data=%d qout= %d",$time,clearb,data,qout);

initial

begin

repeat(5)

begin

data=4'b0000;

`stim 0001;

`stim 0010;

`stim 0011;

`stim 0100;

`stim 0101;

`stim 0110;

`stim 0111;

`stim 1000;

`stim 1001;

`stim 1010;

`stim 1011;

`stim 1100;

#200->end_fist;

end

$finish;

end

endmodule

可综合模块:

(1)

module flop(data,clock,clear,q,qb);

input data,clock,clear;

output q,qb;

nand  nd1(a,data,clock,clear),

         nd2(b,ndata,clock),

                             nd4(d,c,b,clear),

                             nd5(e,c,nclock),

                             nd6(f,d,nclock),

                             nd8(qb,q,f,clear);

nand nd3(c,a,d),

        nd7(q,e,qb);

not   in1(ndata,data),

        iv2(nclock,clock);

         

endmodule

(2)

module hardreg2(d,clk,clrb,q);

input clk,clrb;

input [3:0] d;

output [3:0] q;

flop f1(d[0],clk,clrb,q[0],),

    f2(d[1],clk,clrb,q[1],),

           f3(d[2],clk,clrb,q[2],),

           f4(d[3],clk,clrb,q[3],);

endmodule

4 UPD不可综合。

5 UDP的输出仅能指定为0或1,而不能指定为Z值,当输入组合没有指定时,输出为X。

6 UDP仅有一个输出,输入最多有十个

7 所有端口都必须是一位的。

8 不允许出现高阻状态。

9 而在组合UDP中输出端口却不能定义成寄存器型

10 组合UDP可构成多达10个输入和1个输出的组合功能。UDP的定义仅仅包含一个逻辑真值表形式的表格。UDP的输出仅能指定为0或1,而不能指定为Z值,当输入组合没有指定时,输出为X。UDP定义以关键字primitive开始,之后定义它的名称和端口。在端口列表中的第一个总是UDP的输出端。UDP可以像Verilog原语一样例化,例化时可以指定0个、1个或2个延迟值。

首先看一个Verilog门级原语例化的例子:nand  #(3,5)  gate1(w,i1,i2,i3,i4);

上面例子中例化了一个四输入的与非门,w为输出;tplh(低到高传输)和tphl(高到低传输)时间分别是3和5。门延时是可选的,如果没有指明,则假定延时值为0,如果仅使用一个延迟参数,如#3,或#(3),则该参数值应用于所有门输出转换。指定延迟的最小值用于输出转换到X的延迟。上面的3和5可以看作是固有延迟,我们可以使用min:typ:max延迟格式来替代固有延迟。这种格式称为延迟表达式,定义最小延迟、典型延迟值和最大延迟值,默认情况下typ用于仿真。如果用延迟表达式代替固有延迟,则上面的例化例子可以写成:

nand  #(3:4:5,4:5:6) gate1(w,i1,i2,i3,i4);


用户自定义基元:

通过它来实现门级基元的扩展,可模拟2种行为:
·组合行为,由UDP的组合基元来模拟;
·时序行为,由UDP的时序基元来模拟;
一个UDP可以有多个输入,但只能有一个标量输出,输出可有3种状态:0、1、x,不支持高阻态z,但输入的z态往往被当成x态。
1.定义
UDP的定义与模块无关,与模块属同一层次。
规则:
·只允许有一个输出端口的声明,且必须定义在输入端口的定义之前;
·不准有inout端口,不能定义成向量的形式;
·在时序UDP中,输出端口必须定义成寄存器型,而在组合UDP中输出端口却不能定义成寄存器型;
·逻辑实现主体全部在状态表格table中,每一行所对应的输入激励的顺序都与前面输入端口定义的次序一样,且与端口的实际定义内容无关。在table中每个输入/输出端口都有一个域,输入域和输出域之间用冒号隔开,每一行定义了输入信号所产生的特定组合输出。在时序UDP中,还在输入域和输出域之间加入另一个域,指明UDP目前的状态,可认为是目前UDP的实际输出。没有列出的输入组合所对应的输出都为x。
格式:
primitive 名(接口信号表);
output;    //端口声明;
input;
table       //状态表格描述;
endtable
endprimitive

UDP状态表格
符号  解释           注释
0     逻辑0
1     逻辑1
x     未知逻辑
?     逻辑0、1或x    不能用于输出
b     逻辑0或1       不能用于输出
-     不变化         只用于时序基元
(vw)  从v到w         可以是0、1、x或?
*     同(?)         输入的任何变化
r     同(01)         输入上升沿
f     同(10)         输入下降沿
p     同(01)、(0x)、(x1)  含x的上升沿
n     同(10)、(1x)、(x0)  含x的下降沿

2.组合UDP
例,定义一个组合UDP并例化,
primitive mult(mux,control,data1,data2);
output mux;
input control,data1,data2;
//control data1 data2:mux
0 1 0:1;
0 1 1:1;
0 1 x:1;
0 0 ?:0; //?表示0、1、x三种情况,可简化描述表
1 ? 1:1;
1 ? 0:0;
x 0 0:0;
x 1 1:1;
endtable
endprimitive
该多路选择器例化如下,
module ex1(in1,in2,in3,out1);
input in1,in2,in3;
output out1;
wire out1;
mult mult1(out1,in1,in2,in3);
endmodule

3.时序UDP
将有一个存储元素来存储当前状态。分电平和边沿敏感2种。
(1)电平敏感的时序UDP:比组合型多了一个寄存器,主要用来保存当前的状态,也可以当成是当前的输出。当前的输入和状态确定下一个输出,例:
primitive udp_latch(q1,data,clk);
output q1;
input data,clk;
reg q1;
initial
q1=0; //初始化输出信号为0
table
//data clk : q1(current) q1(next state)
0 0 : ? : -;
0 1 : ? : 0;
1 0 : ? : -; //其中?表示并不关心当前状态
1 1 : ? : 1; //它可以是0、1或x
endtable
endprimitive

(2)边沿敏感的时序UDP:某输入的跳变触发输出的改变,表格中每一行只能有一个输入的跳变,D触发器例如:
primitive d_edge(q1,data,clk);
output q1;
input data,clk;
reg q1;
initial
q1=0;
table
//data clk : q1(current) q1(next)
0 (01) : ? : 0;//选通,上升沿
1 (01) : ? : 1;
//no change in output values
0 (0x) : ? : -;//不选通,没有上升沿
1 (0x) : ? : -;
//no change for negedge
? (?0) : ? : -;//不选通,可能是下降沿
//no change for change in data
(?) ? : ? : -;//输入的变化不影响输出
endtable
endprimitive

例2,T触发器,
primitive T_FF(q1,clk,clear);
output q1;
input clk,clear;
reg q1;
table
//clk clear : q1(current) q1(next)
?    1 : ? : 0;
? (10) : ? : -;//忽略clear的下降沿
(10) 0 : 1 : 0;//下降沿触发翻转
(10) 0 : 0 : 1;
(0?) 0 : ? : -;//忽略时钟的上升沿
endtable
endprimitive

构成4位循环计数器:
module counter4(Q,clk,clear);
//IO ports
output [3:0] Q;
input clk,clear;
T_FF tff0(Q[0],clk,clear);
T_FF tff1(Q[1],Q[0],clear);
T_FF tff0(Q[2],Q[1],clear);
T_FF tff0(Q[3],Q[2],clear);
endmodule

(3)混合时序UDP:允许同时定义2中表,当输入变化时,优先处理边沿触发事件,再处理电平事件;若某激励同时触发2种事件则输出结果以电平事件为准(可理解为后修改的结果)。
JK触发器描述的混合时序UDP:
primitive jk_edge(q,clk,j,k,preset,clear);
output q;
input clk,j,k,preset,clear;
reg q;
table
//clk j k preset clear : q(state) q(output)
? ? ? 0 1 : ? : 1;//preset
? ? ? * 1 : 1 : 1;
? ? ? 1 0 : ? : 0;//clear
? ? ? 1 * : 0 : 0;
r 0 0 0 0 : 0 : 1;//normal clocking case
r 0 0 1 1 : ? : -;
r 0 1 1 1 : ? : 0;
r 1 0 1 1 : ? : 1;
r 1 1 1 1 : 0 : 1;
r 1 1 1 1 : 1 : 0;
f ? ? ? ? : ? : -;
b * ? ? ? : ? : -;
b ? * ? ? : ? : -;
endtable
endprimitive

实际上原语在编写可综合代码中并不使用,UDP是不可综合的。


弄错了,把更新的内容贴到回复里了,更新帖子就在主贴里,不会放到回复里了哦

主题内容在主贴陆续更新,,,

已更新至第五章

小编继续更新吧!我追着呢

jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj

谢谢小编分享!

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top