Verilog语法(标量、向量,数组、寄存器组,存储器)——赋值、读写操作、常见误区
一、Verilog通过对reg型变量建立数组来对存储器进行建模,可以描述RAM,ROM存储器和寄存器数组
//格式
reg[n-1:0]存储器名[m-1:0];
//reg[n-1:0]是定义了每一个存储器单元大小,为n位宽 [m-1:0]定义了存储器大小,即有多少存储器
reg [n-1:0]rega;//一个n位寄存器
reg memb[m-1:0];//n个一位寄存器
rega = 0; //合法
memb = 0; //不合法
//Verilog 中允许声明 reg, wire, integer, time, real 及其向量类型的数组。
//维数没有限制。线网数组也可以用于连接实例模块的端口。数组中的每个元素都可以作为一个标量或者向量,以同样的方式来使用,形如:<数组名>[<下标>]。对于多维数组来讲,用户需要说明其每一维的索引。例如:
integer flag [7:0] ; //8个整数组成的数组
reg [3:0] counter [3:0] ; //由4个4bit计数器组成的数组
wire [7:0] addr_bus [3:0] ; //由4个8bit wire型变量组成的数组
wire data_bit[7:0][5:0] ; //声明1bit wire型变量的二维数组
reg [31:0] data_4d[11:0][3:0][3:0][255:0] ; //声明4维的32bit数据变量数组
二、标量、向量,数组、寄存器组,存储器
1、标量、向量
在声明wire或reg变量时,如果不指定范围,那么此时的wire/reg就是一个标量(scalar);反之,如果指定了范围,那么此时的wire/reg就是一个向量(Vector)。
wire [3:0] n0; //4位宽的向量wire
wire n1; //单位宽的标量wire
reg [3:0] d0; //4位宽的向量reg
reg d1; //单位宽的标量reg
向量最左端的值被称为最高有效位MSB,最右端的值被称为最低有效位 LSB。MSB和LSB的值可以是任意整数(正整数、负整数和零),而且MSB即可以大于LSB,也可以小于LSB,但两者不能相等(相等就是标量了)
对于向量,可以根据其地址,来对某一位或某几位进行操作
位操作和部分操作
reg [31:0] addrs; //声明一个8位宽的reg型变量(向量)addrs
addrs [0] = 1'b1; //直接对addr的第0位进行操作,将其赋值为1
addrs [4] = 1'b0; //直接对addr的第4位进行操作,将其赋值为0
addr[23:16] = 8'h22; //直接对addr的第16~23位进行操作,将其赋值为8'h22
Verillog 还支持指定 bit 位后固定位宽的向量域选择访问。
[bit+: width] : 从起始 bit 位开始递增,位宽为 width。
[bit-: width] : 从起始 bit 位开始递减,位宽为 width。
//下面 2 种赋值是等效的
A = data1[31-: 8] ;
A = data1[31:24] ;
//下面 2 种赋值是等效的
B = data1[0+ : 8] ;
B = data1[0:7] ;
2、数组、寄存器组
在 Verilog 中可以声明 reg, wire, integer, time, real类型的数组(Array)。数组维数没有限制,其中的每个元素可以是标量或者向量。
integer flag [7:0] ; //8个整数组成的数组
reg [3:0] counter [3:0] ; //由4个4bit计数器组成的数组
wire [7:0] addr_bus [3:0] ; //由4个8bit wire型变量组成的数组
wire data_bit[7:0][5:0] ; //声明1bit wire型变量的二维数组
reg [31:0] data_4d[11:0][3:0][3:0][255:0] ; //声明4维的32bit数据变量数组
数组使用
1、定义
reg [w : 0] r_addrss, w_addrss; //读和写的地址
reg [wordsize : 0] array_name [2**w-1 : 0];
reg [wordsize : 0] data_in, data_out;
reg data_out_0;
2、写和读操作
my_memory[address] = data_in; //写操作
data_out = my_memory[address]; //读操作
3、读出某单元的具体位
对某单元具体一位进行操作
方法一 直接索引 [单元索引][位索引]
reg [7:0] my_array[0:3];
initial begin
my_array[0][3] = 1'b1; // 写数组中的第一个单元的第 4 位
wire single_bit = my_array[0][3]; // 读数组中的第一个单元的第 4 位
end
方法二 利用中间变量
data_out = my_memory[address];
data_out_it_0 = data_out[0];
读或写某单元连续多位
reg [7:0] my_array[0:3];
initial begin
wire [3:0] multiple_bits = my_array[0][3:0];
// 进行相应的处理
end
4、初始化
初始化内存有多种方式,这里介绍的是使用 $ readmemb 和 $ readmemh系统任务来将保存在文件中的数据填充到内存单元中去。
$ readmemb 和 $ readmemh是类似的,不过 $ readmemb 用于 内存的二进制 表示,而 $ readmemh 则用于内存内容的16进制 表示。这里以$readmemh系统任务来介绍。
$readmemh("file_name", mem_array, start_addr, stop_addr);
- file_name是包含数据的文本文件名
- mem_array是要初始化的内存单元数组名
- tart_addr 和 stop_addr是可选的,指示要初始化单元的起始地址和结束地址。
// 例子
module memory ();
reg [7:0] my_memory [0:255];
initial begin
$readmemh("memory.list", my_memory);
end
endmodule
这里使用内存文件memory.list来初始化my_memory数组。
而下面就是一个内存文件的例子。
// Comments are allowed
CC // This is first address i.e 8'h00
AA // This is second address i.e 8'h01
@55 // Jump to new address 8'h55
5A // This is address 8'h55
69 // This is address 8'h56
对于内存文件,要注意的是下列几点:
a、注释标记//在内存文件中是被允许的;
b、使用@符号将跳到新的目标地址,没有@符号就表示地址将顺序递增。
如上述的内存文件就只初始化8’h00,8’h01,8’h55和8’h56 4个内存地址单元
关于系统任务,有下列常见的用法:
-
顺序初始化所有的数组单元;
这种情况下,可以使用@符号来指示地址,也可以不使用它,而只在每一行存放要存放的数据。 这样数据将顺序按地址递增存放,从0地址开始。
-
只初始化部分的数组单元;
这种情况下,可以使用@符号来指示下一个要初始化的地址,然后对该地址单元进行初始化
-
只初始化数组的地址区间的一部分单元。
这个时候,还可以使用$readmemh任务的start_addr 和 stop_addr选项来指定初始化的范围。
例如,只初始化100到104这5个单元,就可以这么做:
内存文件memory.list定义为:
CC
AA
55
5A
69
而$readmemh(“memory.list”, my_memory, 100, 104);就指定使用memory.list来初始化my_memory的100-104单元
5、使用常见误区
reg [n-1:0]rega;//一个n位寄存器
reg memb[m-1:0];//n个一位寄存器
rega = 0; //合法
memb = 0; //不合法,寄存器组有多个数组不许一次性进行赋值
reg a1[3:0]; //1维数组,共有4个元素,每个元素位宽为1(即标量)
reg [3:0] a2[3:0]; //1维数组,共有4个元素,每个元素位宽为4
reg [3:0] a3[3:0][15:0]; //2维数组,共有4*16 = 64个元素,每个元素位宽为4
数组与向量一样,也可以通过地址索引的方式进行访问。
a1 = 0; //非法赋值--不能同时对4个元素赋值
a1[0] = 1'b0; //对a1的第0个元素赋值为1'b0
a2[2] = 4'b1010; //对a2的第2个元素赋值为4'b1010
a3[3] [1] = 4'b1111; //对a3的第3行、第1个元素赋值为4'b1111
三、存储器
存储器(Memory)就是一种寄存器数组,数组的维数不能大于2,它可以用来构建 RAM(可读写) 或 ROM(仅可读)