chisel学习--用到的Verilog语法细节
数据类型
逻辑值的概念
逻辑值:4种逻辑值
0 | 1 | X | Z |
---|---|---|---|
假 | 真 | 不确定 | 高阻,浮动状态 |
强度值:用以解决不同强度驱动源之间的赋值冲突
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
Supply | Strong | Pull | Large | Weak | Medium | Small | Highz |
数值的表示
形式:<位宽>‘<进制><数值>
申请方式: integer
进制 | 二进制 | 八进制 | 十进制 | 十六进制 |
---|---|---|---|---|
表示 | b | o | d | h |
举例 | 4’b1011(4位) | 6‘o87_25(6位,_忽略) | 8’12(8位,d可省) | ‘h2f(32位) |
x/z | 代表1位 | 代表3位 | - | 代表4位 |
线网的概念
线网:类似实际使用的电线,数值一般只能通过连续赋值,由赋值符右侧连接的驱动源决定。
申请方式: wrie
wrie | supply0 | supply1 | tri | tri0 | tri1 | trireg | triand | trior | wand | wor | uwire |
---|---|---|---|---|---|---|---|---|---|---|---|
默认,连接单元的连线 | 低电平0 | 高电平1 | 多个驱动源驱动同一根线 | 强度为pull的0值作驱动 | 强度为pull的1值作驱动 | 0,1,x三态驱动源 | 某个驱动源为0则值为0 | 某个驱动源为1则值为1 | 与triand一致 | 与trior一致 | - |
寄存器的概念
寄存器: 存储数值的变量,初始值为x
申请方式:reg
输入-> 输出 | 线网 | 寄存器 |
---|---|---|
线网 | 允许 | 不允许 |
寄存器 | 允许 | 不允许 |
向量的概念
对于线网wire或寄存器reg类型:
标量:没有声明位宽的量
向量:声明位宽范围并且位宽范围大于1的量
申请方式:<类型名称> [n:0] <自定义的名字>
使用时:<自定义的名字>[i]
- | 标量 | 向量 | 特定位赋值 | 位溢出 | 缺位宽 |
---|---|---|---|---|---|
操作 | wire b; | reg [3:0] c; | c[2:0]=3’b101 | 舍弃最左边 | 最左边补0 |
数组的概念
对于reg,integer,time,real,realtime类型
数组:维度没有限制,元素为向量或者标量
申请方式:<类型名称> [n:0] <数组名字> [0:m]
使用时:<数组名字> [j]
- | 可用数据类型 |
---|---|
向量 | wire,reg |
数组 | reg,integer,time,real,realtime |
区别 | 向量是一个单独的元件,位宽是n;数组由多个元件组成,共长m,其中每个元件的位宽为n或者1 |
参数的概念
参数:参数parameter和局部参数localparam
用途:在模块内定义常数,不能赋值
- | 参数重定义的方式和范围 |
---|---|
parameter | 通过“#”进行重新定义 |
localparam | 不可以重定义 |
非常见运算符
按位与 | 按位或 | 按位异或 | 按位同或 | 缩减操作符 | 逻辑移位 | 算术移位 | 拼接 | 重复拼接 | 条件 |
---|---|---|---|---|---|---|---|---|---|
a&b | 竖杠 | a^b | ^~ / ~^ | 所有位参与 | >>或<< | 符号位可填充 | {a,b} | {n{a}} | ?: |
模块的概念
简单模块
模块:module,相当于函数,实际意义是代表硬件电路上的逻辑实体
形如:
module <模块名>(<输入输出端口列表>);
<定义,指定数据对象为寄存器型、存储器型、线型以及过程块,相当于初始化>
<模块条目,将上面定义和端口组合起来,说明这个模块要做什么,相当于计算过程>
endmodule
举例:
module add_sub #(
parameter DATAWIDTH = 8 //参数
)(
input clock,
input reset,
input i_add_sub,
input signed [DATAWIDTH-1:0] i_a,
input signed [DATAWIDTH-1:0] i_b,
output reg signed [DATAWIDTH:0] o_result, //排列的输入输出的端口声明
);
always@(posedge clock or negedge reset) //设置输入激励
begin //计算过程开始
if( !reset )
o_result <= 9'd0;
else if( i_add_sub )
o_result <= i_a - i_b;
else
o_result <= i_a + i_b;
end //计算过程结束
endmodule
模块例化
模块例化:将上述定义的模块module例化,相当于创建一个实例
形式如下(与上节定义的函数对应):
add_sub #(
.DATAWIDTH(16) //参数由8改动为16
) add_sub_01(
.clock (clock_01),
.reset (reset_01),
.i_add_sub (i_add_sub_01),
.i_a (i_a_01),
.i_b (i_b_01),
.o_result (o_result_01),
);
遵循的规则 | 端口连接 | 位宽匹配 | 未连接端口 | 外部信号 | 带参数的例化 |
---|---|---|---|---|---|
做法 | 对模块内部,输入只能线网,输出线网或reg;对模块外部刚好相反 | 最好一致,不一致时最好将宽位宽截位 | 允许未连接状态 | 顺序对应时可省略内部端口的书写 | 参数改与不改灵活掌握 |
模块测试
模块测试:可使用一个顶层模块(通常称为测试平台Testbench),通过实例调用所定义的模块来进行测试,相当于test函数
特点:由于这个顶层模块不需要被上层调用,没有输入输出端口
举例:
module add_sub_tb;
reg clock,
reg reset,
reg i_add_sub,
reg signed [7:0] i_a,
reg signed [7:0] i_b,
wire signed [7:0] o_result, //对于外部,输出只能是wire
);
always # 5 clock <= ~clock; //生成时钟
always@(posedge clock or negedge reset) //设置输入激励
//生成输入信号
begin
if( !reset )
begin
i_a <= 8'd0;
i_b <= 8'd0;
end
else
begin
i_a <= $ random;
i_b <= $ random;
end
end
//实例调用add_sub模块,名字为add_sub
add_sub add_sub(
.clock (clock),
.reset (reset),
.i_add_sub (i_add_sub),
.i_a (i_a),
.i_b (i_b),
.o_result (o_result),
);
initial //初始化输入
begin
clock = 0;
reset = 0;
i_add_sub = 0;
i_a = 0;
i_b = 0;
# 100;
reset = 1;
#1000;
i_add_sub = 1;
end
endmodule
过程语句
两个过程
always过程:不断循环执行,除非使用**$ finish**
initial过程:关键字开始后只能执行一次
特点:一个模块可以包含多个过程,多个过程互相之间并发执行,过程不能嵌套,多条语句需要使用关键字包裹成代码块
代码块:内部代码一体,可以通过关键字决定顺序执行或并行执行,代码块可以嵌套
代码块包装 | begin-end | fork-join |
---|---|---|
说明 | 顺序代码块 | 并行代码块 |
赋值
寄存器变量的过程赋值
阻塞赋值: 符号为 =,该赋值未成功,后边语句不会执行
非阻塞赋值: 符号为** <= **,该句执行时不会阻碍下一语句执行,并且后一句涉及前边的非阻塞赋值变量时,两个语句会同时执行,且后句用到的是前一个语句执行前的数值。
线网变量的连续赋值
连续赋值:将标量或者向量的数值驱动给网线,右边的值有变化,马上就将值赋给左边的变量
使用方式:使用关键字assgin
特点:不能像寄存器那样存储当前数值,需要驱动源提供信号,且驱动源是连续不断的、
举例比较(假设a=0,b=1):
赋值操作 | 阻塞赋值 | 非阻塞赋值 | 连续赋值 |
---|---|---|---|
举例 | a = b; b= a | a<=b ; b<=a | wire a,b,y; assign y= a&b |
结果 | a=1,b=1 | a=1,b=0 | y随a,b而一直变化 |
时序控制
延时时序控制
延时的参考时间:是这一句本该开始执行的时间
常规延时:赋值语句左边,先进行延时,延时完成后再计算表达式
内嵌延时:赋值语句右边,先立即计算表达式,再进行延时,最后把表达式结果赋值给左边
使用方式:使用关键字 # ,“# <延时的时间数字>”
事件时序控制
事件时序控制:如果指定事件发生,则代码被触发执行
指定事件:若是变量,则指的是产生变化时触发;若是指定触发条件如posedge(上升沿)或者negedge(下降沿),则是满足条件时触发;若是监视多个变量时,可用关键字 or 或 “,”,任意一个变量发生变化才触发
使用方式:使用关键字 @,“@(变量或事件名称)”
电平敏感时序控制
电平时序控制:电平或者变量为真时,执行后边的代码块
时序控制比较:
时序控制 | 举例 | 结果 |
---|---|---|
常规延时 | # 5 x = 3; | 5个系统周期延时后将x赋值为3 |
变量常规延时 | parameter latancy=8; # latency x = 3 | 8个系统周期延时后将x赋值为3 |
内嵌延时 | z = # 8 (x + y); | 当前时刻的x,y计算结果,8个系统周期延时后赋给z |
变量事件延时 | @(clk) x = 1; | 变量clk发生变化时将1赋值给x |
触发条件事件延时 | @(posedge clk) x=1 | 变量clk上升沿时将1赋值给x,(下降沿为negedge) |
多事件敏感延时 | @(a or b or c) 或@(a , b) | a或b或c任一变化时,触发代码块 |
所有事件敏感延时 | @ * 或 @(*) | 对所有输入变量敏感 |
电平延时 | wait(a) | 变量a为真,执行后边代码块 |