FPGA基础设计(8)Verilog常数赋值、字符串、标识符

阅读《IEEE Standard for Verilog 2005》时,做一些整理和记录。


1.整数赋值

按照Verilog 2005的标准:0-9、a-f、z、x称作数字位(digit);表示数字正负的’+‘和’-‘视作一元操作符(unary operator);常说的二进制、八进制、十进制、十六进制称作数字的基(base);其在Verilog中的表示’b’、‘o’、‘d’、'h’称作基格式(base format)字符;表示常数的bit数称作size。Verilog使用到的字符都是不区分大小写的。

另外要明白二进制数本身的运算规则与一个数是signed还是unsigned、小数点在哪个位置没有任何关系,这些只是设计者人为的规定。但是在代码设计时,要标识清楚数据是signed或unsigned,目的有两个:(1).对设计者的提示;(2).一些操作符、IP核的数据端口,对数据是signed或unsigned的“身份”有要求。

如果一个数是负数(前面加了’-’),那么它必定采用的是二进制补码形式。

方式1

常数赋值有两种,一种为直接赋值(符号+一串数字位),采用这种方式赋值时,默认数据是sigend形式。如下面一个简单的示例:

module test
(
    input clk,
    output reg [31:0] c
);

wire [31:0] a, b;

assign a = -595;
assign b = -10;

always @ (posedge clk)
    c <= a + b;

endmodule 

对两个带符号数做加法运算,结果仍然是带符号数,Vivado中仿真时,将Radix设置为Signed Decimal,可以看到变量C的结果为-605。

方式2

另一种则是常见的基常数赋值方法(符号+size+单引号+基格式字符+一串数字位),如"assign a = +16’d53;", 符号和size是可有可无的。

如果在基格式字符(b、o、d、h)前加上’s’,表示这是个带符号数signed;没有加’s’则表示是不带符号数unsigend。如果前面加了符号位“-”,会自动用二进制补码的形式。

如下面的简单示例:

`timescale 1ns / 1ps

module test
(
    input clk,
    output reg [31:0] c
);

wire [31:0] a, b;

assign a = -32'd595;   // 加上size,省去s
assign b = -'sd10;      // 省略size,加上s

always @ (posedge clk)
    c <= a + b;

endmodule 

仿真结果与第一个示例相同。

位宽扩展

这里先不讨论将低位宽的数据赋值给高位宽数据的问题,比如将12bit的a赋值给16bit的b。先讨论直接将低位宽的常数赋值给高位宽的数据时,位宽扩展遵循怎样的标准(不考虑x和z)。

以下面的测试代码为例,将12bit的常数赋值给16bit的数据:

wire [15:0] a = 12'd565;
wire [15:0] b = -12'd565;
wire [15:0] c = -12'sd565;
reg [15:0] d = -12'sd565;

仿真结果如下,分别展示了赋值结果的2进制和10进制:

对于正数,赋值结果为高位补0。对于负数(二进制补码),无论是reg还是wire型、加s声明为带符号数或者不加s,结果都是高位补符号位。可见位宽扩展只与数据的正、负有关。Verilog HDL的位宽扩展机制可以确保设计者安全、便捷地完成程序设计。

“负数”与“带符号数”的区别

由于负数必须需要符号’-'来规定,所以第一感觉就是将负数和带符号数二者等同。其实两者并不完全相同,如果只加负号,不加s,虽然软件工具会将其用二进制补码表示,但仍不会将其当作带符号数signed看待。看下面的示例代码:

module test
(
    input clk,
    output reg [15:0] a,b,c,d
);

always @ (posedge clk) begin
    a <= -12 / 3;
    b <= -'sd12 / 3;
    c <= -'d12 / 3;
    d <= -4'sd12 / 3;
end

endmodule

仿真结果如下图:

  • 对于a:在没有规定大小和基格式时,-12和3都视作带符号数,因此结果为正确的-4;

  • 对于b:-'sd12使用字符’s’声明这是个带符号数,结果同样是正确的-4;

  • 对于c:-'d12虽然声明这是个负数,但没有加-s,软件仍然不会将其视作带符号数,运算结果与预期不符;

  • 对于d:-4’sd12同时还指定了大小,但4’sd12已经超过了4bit带符号数的表示范围,它的值12(1100)实际意义上是-4(最高位符号位表示负数,二进制补码),前面再加’-'为正4,除以3,近似为整数后值为1。


2.实常数赋值

Verilog HDL本身是支持在代码中使用小数、科学计数法的,只不过赋值给整数型数据时,会发生隐式转换,转换为整数。看下面的示例:

wire [15:0] a = 1e4;
wire [15:0] b = 12.1;
wire [15:0] c = 12.5;
reg [15:0] d = -12.5;

仿真结果如下:

数据a用科学计数法进行赋值。隐式转换的规则是:(1). 转换为最接近的整数;(2).中间值.5转换时,向远离0的方向进行。如数据c,12.5 => 13;数据d,-12.5 => -13。


3.字符串(String)

Verilog HDL同样也支持字符串的使用,使用双引号“”表示字符串内容,一个字符串必须放在一行内。在表达式和赋值语句中使用字符串,工具会将其视作无符号整数,一个字符对应一个8bit的ASCII码。

\n、\r、\t、\\和\"等常用的转义字符,Verilog HDL也同样支持。

下面是一个简单的测试代码:

module test
(
    input clk,
    output reg [8*4-1:0] c,
    output [8*4-1:0] d
);

assign d = "CUIT";
always @ (posedge clk)
    c <= "CUIT";

endmodule 

仿真时可以看到,无论是reg还是wire类型,其存储的值本质都是“32’h43554954”,是一串ASCII码的组合。

设计仿真激励文件时,字符串更多是一种让仿真结果更直观的辅助手段(如用在$display系统任务中)。硬件设计中也有一定用处,如在一些通信协议的设计中,直接使用字符串操作会便捷很多,起码不用对着ASCII码表查找。

由于字符串的本质仍然是无符号整数,因此Verilog的各种操作运算对字符串也适用。比如用==和!=进行字符串的比较、用{ }完成字符串的拼接。不过需要注意:当字符串赋值的对象位宽较大时,左边会做补0处理,这样会对字符串的比较、拼接造成影响,结果可能与我们的预期不符,如下面示例代码:

module test
(
    input clk,
    output  reg  [7:0] a,b,c,d
);

reg [8*8-1:0] s1 = "Hello", s2 = " World!";
reg [8*5-1:0] s3 = "Hello";
reg [8*7-1:0] s4 = " World!";

always @ (posedge clk) begin
    if ({s1,s2} == "Hello World!")
        a <= 1'b1;
    else a <= 1'b0;
        
    if ({s3,s4} == "Hello World!")
        b <= 1'b1;
    else b <= 1'b0;
end

endmodule 

看起来s1和s2的拼接、s3和s4的拼接结果都应该是“Hello World!”,但仿真结果如下:

!

事实上,s1和s2进行字符串赋值时先要高位补0,再拼接后的值已经不是单纯的“Hello Wrold!”。s3和s4的位宽正好与字符串长度相等,赋值时没有补0,因此比较结果相等。

ASCII码有个特殊的字符NUL,含义为空,码值为0。Verilog字符串中使用("")或("\0")都是表示NUL的含义,不会占据实际的存储空间。比如 reg [5*8-1:0] = “Hello\0”; 和 reg [5*8-1:0] = “Hello”; 的含义完全相同。


4.标识符(identifier)

常说的“数据名”、“变量名”,在标准中的规范名称应该是“identifier”。常见的标识符由字母、数字、$、下划线组成,其中数字和$不能是标识符的第一个字符。

标识符其实有“最大长度限制”,不同软件可以自行规定,不过这个限制值最小也有1024个字符。

这里专门提到标识符,是想记录一下比较陌生的转义标识符(escaped identifier),不过一般也用不到Verilog的这个特性。

一般的标识符对使用到的字符有限制,转义标识符可以使用所有可显示的ASCII字符。转字标识符必须以’\'为开头以空格、Tab键盘、换行其中之一为结尾

看下面的简单示例代码,注意表达式和赋值语句的分号前都加了空格:

module test
(
    input clk,
    output [23:0] c,
    output d
);

reg \75 ;
reg [23:0] \{a+b} ;
reg \always ;
reg \flag ;

always @ (posedge clk) begin
    \75 <= 1'b1;
    \{a+b} <= 23'd565;
    \always <= 1'b1;
    flag <= 1'b0;
end

assign d = \75 ;
assign c = \{a+b} ;

endmodule 

如果转义标识符中没有用到其它特殊字符,则本质上仍然是一般的标识符。上面的代码中,定义时为 \flag,使用时直接用 flag 即可。

有个特殊情况,就是转义字符直接使用Verilog HDL中保留的关键字,如上面的 \always,不过使用时前面的’\'不能省略。

### Verilog 中移位运算符和赋值语句 #### 移位运算符 在Verilog中,移位运算符用于将二进制数按照指定的数量向左或向右移动。这类似于C语言中的移位操作,但在硬件描述方面更为具体[^1]。 对于逻辑移位,在`<<`表示左移,而`>>`代表无符号右移;对于算术移位,则使用`>>>`来保持最高有效位不变地执行有符号右移。这些特性使得设计者能够精确控制数据流的方向以及处理带符号数值的情况[^2]。 ```verilog // 定义寄存器变量a, b, c, d并初始化为8'b0000_0011 (即十进制3) reg [7:0] a = 8'b0000_0011; wire [9:0] b; // 左移一位后的结果应为6(二进制110), 需要更宽的数据类型存储 assign b = a << 1; // 右移两位的结果将是0(因为原值只有最低两位非零) wire [7:0] c; assign c = a >> 2; // 对于负数情况下的算术右移 reg signed [7:0] e = -8'd4; // 假设e是一个带有符号的字节(-4) wire [7:0] f; assign f = e >>> 1; // 结果应该是-2 (-4除以2),高位填充符号位 ``` #### 赋值语句 赋值可以分为阻塞型(`=`)和非阻塞型(`<=`): - **阻塞型赋值**:当遇到这样的语句时,程序会立即执行该指令并将右侧表达式的当前值赋予左侧的目标信号/变量。这意味着在同一过程中后续的操作将会看到这个新值。 - **非阻塞型赋值**:这种类型的赋值不会立刻改变目标信号的状态,而是安排在未来某个时间点更新其状态。这对于模拟并发行为特别有用,尤其是在组合逻辑电路的设计当中。 ```verilog always @(posedge clk or negedge rst_n) begin : proc_name if (!rst_n) q <= 1'b0; // 复位状态下q被设置成低电平 else q <= d; // 同步加载输入d到输出q上 end initial begin reg_var = some_value; // 这里采用的是阻塞性赋值方式 end ```
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值