相关阅读
Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html?spm=1001.2014.3001.5482
$readmemb和$readmemh两个系统函数用于将文件中的数据加载到存储器或者被称为数组的memory中。首先给出他们的语法的BNF范式,有关BNF范式的内容可以在之前的文章中找到:
首先要注意的是,作为系统函数的一类,这些语句都是能在过程块initial和always中使用的。
load_memory_tasks分为两类,一类是以二进制解析文件中的数据,而另一类是以十六进制解析。特别提醒,$readmemb和$readmemh中美元符$和后面的关键词之间必须连续,不能有空白。
file_name指的是要读取数据的文本文件名,文件名可以使用绝对路径或相对路径,相对指的是相对当前仿真器的工作目录(一般为项目文件所在文件夹)。
要读取数据的文件有以下三点要求:
- 用于分隔数字的空白字符(空格、换行符、制表符、换页符)
- 注释(两种形式的注释都可以,即//或/* */)
- 二进制或十六进制数字(这里指的是纯数字,不含任何其他指示性的符号,即111,fff这种,且不能包含正负号+、-)
memory_name指的是需要初始化的存储器名,数字的长度不能高于存储器的数据位宽,否则当读取到不符合要求的数据会报错,无法继续读取。未知值(x或X)、高阻抗值(z或Z)和下划线(_)可用于指定数字,应使用若干空白字符和/或注释来分隔数字,下面是一些例子。
test.v
module test();
reg [7:0]data[7:0];//和reg [7:0]data[0:7];效果相同
initial
$readmemb("../data.txt", data);
endmodule
data.txt
//前5个数据正常读取,直到0b报错无法解析
11 01 11111 11 100 0b101 110 111 1000 001 100001
//第一个数据太大了,报错无法解析
11111111111 11 01 11111 11
//可以在数字中插入_
11_00 1122 2344
//可以用换行符分隔
11
22
33
//可以用注释分隔数字
11/*55555*/01 11111 11 100
//可以在数字中使用x或z
z1 x1 11 12
当指定的数字位宽小于存储器的位宽时,将会进行位宽拓展,这里的拓展规则只和数据有关而与存储器变量的类型无关,类似于Verilog基础:表达式中的整数常量(integer)一文中“无符号数字的位宽小于位宽常数指定的大小”一样,如果数字最高位是1或0,则补0至存储器的数据位宽,如果是z则补z,如果是x则补x。
当文件数据被从上至下,从左至右读取时,每个数据被分配给索引从低到高的各个存储器变量,寻址可以通过在系统任务调用中指定开始和/或结束地址以及在数据文件中使用符号指定地址来控制。
当地址出现在数据文件中时,格式为@字符,后跟十六进制数字,如下所示:
@hhh
@HHH
@HhH
@ 1h1 //错误
数字中允许同时使用大写和小写数字。@和数字之间不允许有空格,可以使用数据文件中所需的任意多的地址规范,当系统任务遇到地址规范时,它会从该内存地址开始加载后续数据,并将数据填入对应地址索引的寄存器变量中。
文件中的所有数字都有默认的地址,分为几种情况:
- 如果系统函数中没有规定起始地址和终止地址,数据文件中也没有显式的地址,则数据的默认地址为从寄存器变量的最小地址开始依次加1。读取将从寄存器变量的最小地址开始依次读取后面的数据,直到有一个数据的默认地址大于寄存器变量的最大地址(因为此时的数据地址范围要求为从寄存器变量的最小地址到最大地址之间),或者数据读完。举例说明如下所示。
test.v
module test();
reg [7:0]data[7:1];
initial
$readmemb("data.txt", data);//地址范围1到7
endmodule
data.txt //data[5], data[6], data[7]未初始化
00 //地址为1
01 //地址为2
10 //地址为3
11 //地址为4
data.txt //恰好全部初始化
00 //地址为1
01 //地址为2
10 //地址为3
11 //地址为4
00 //地址为5
01 //地址为6
10 //地址为7
data.txt //全部初始化
00 //地址为1
01 //地址为2
10 //地址为3
11 //地址为4
00 //地址为5
01 //地址为6
10 //地址为7
11 //地址为8,当试图读取这个数时会提示超出范围(Too many data words read on line 8 of file)
//并结束读取
- 如果系统函数中只规定了起始地址(它必须在存储器的地址范围内),数据文件中也没有显式的地址,则数据的默认地址为从起始地址开始依次加1。读取将从起始地址开始依次读取后面的数据,直到有一个数据的默认地址大于寄存器变量的最大地址(因为此时的数据地址范围要求为从寄存器变量的最小地址到最大地址之间),或者数据读完。举例说明如下所示。
test.v
module test();
reg [7:0]data[7:1];
initial
$readmemb("data.txt", data, 2);//起始地址为2(必须在1到7之间),则地址范围是2到7
endmodule
data.txt //data[1]和data[6], data[7]未初始化
00 //地址为2
01 //地址为3
10 //地址为4
11 //地址为5
data.txt //data[1]未初始化
00 //地址为2
01 //地址为3
10 //地址为4
11 //地址为5
00 //地址为6
01 //地址为7,读取完这个数据遇到文件结束,结束读取
data.txt //data[1]未初始化
00 //地址为2
01 //地址为3
10 //地址为4
11 //地址为5
00 //地址为6
01 //地址为7
10 //地址为8,当试图读取这个数时会提示超出范围(Too many data words read on line 7 of file)
//并结束读取
- 如果系统函数中规定了起始地址和结束地址(它们必须在存储器的地址范围内),数据文件中也没有显式的地址,则数据的默认地址为从起始地址(因此它必须在存储器的地址范围内)开始依次加1或减1,这取决于起始地址和结束地址的大小,如果结束地址大于起始地址,则为加1,否则为减1。读取将从起始地址开始依次读取后面的数据,直到有一个数据的默认地址大于寄存器变量的最大地址,或小于最小地址(因为此时的数据地址范围要求为从寄存器变量的最小地址到最大地址之间),或者数据读完。举例说明如下所示。
test.v
module test();
reg [7:0]data[7:1];
initial
$readmemb("data.txt", data, 2, 5);//起始地址为2,终止地址为5(必须在1到7之间),则地
//址的范围是2到5,默认地址递增
endmodule
data.txt //data[1], data[5], data[6], data[7]未初始化
00 //地址为2
01 //地址为3
10 //地址为4
data.txt //data[1]未初始化
00 //地址为2
01 //地址为3
10 //地址为4
11 //地址为5
00 //地址为6
01 //地址为7,读取完这个数据遇到文件结束,结束读取
data.txt //data[1]未初始化
00 //地址为2
01 //地址为3
10 //地址为4
11 //地址为5
00 //地址为6
01 //地址为7
10 //地址为8,当试图读取这个数时会提示超出范围(Too many data words read on line 7 of file)
//并结束读取
test.v
module test();
reg [7:0]data[7:1];
initial
$readmemb("data.txt", data, 5, 2);//起始地址为5,终止地址为2(必须在1到7之间),则地
//址的范围是2到5,默认地址递减
endmodule
data.txt //data[1], data[2], data[6], data[7]未初始化
00 //地址为5
01 //地址为4
10 //地址为3
data.txt //data[1], data[6], data[7]未初始化
00 //地址为5
01 //地址为4
10 //地址为3
11 //地址为2,读取完这个数据遇到文件结束,结束读取
data.txt //data[1], data[6], data[7]未初始化
00 //地址为5
01 //地址为4
10 //地址为3
11 //地址为2
00 //地址为1,当试图读取这个数时会提示超出范围(Too many data words read on line 5 of file)
//并结束读取
01 //地址为0
- 现在我们来看数据文件中有显式的地址标识时的情况,第一个地址标号规定了其前后的地址,后面的地址标号规定了其后的地址,根据上面的规则,可以是递增也可以是递减。如下所示。
data.txt //系统函数中起始地址小于终止地址,或没有终止地址,或没有起始和终止地址,则递增
00 //地址为-2
01 //地址为-1
10 //地址为0
@1
00 //地址为1
01 //地址为2
10 //地址为3
@7
00 //地址为7
01 //地址为8
10 //地址为9
data.txt //系统函数中起始地址大于终止地址,则递减
00 //地址为9
01 //地址为8
10 //地址为7
11 //地址为6
@5
00 //地址为5
01 //地址为4
10 //地址为3
11 //地址为2
@6
00 //地址为6
01 //地址为5
10 //地址为4
11 //地址为3
在读取有地址标号的文件时,首先会定位到地址和起始地址相同的那条数据(当没有起始地址时,是定位到存储器对应的最小地址,上面的第一种情况中为1),然后开始从左到右,从上到下,一条一条数据依次读取数据,直到文件结束(所以可能会出现初始化覆盖的情况,如下所示),或者有数据的地址超出了范围才结束读取。
test.v
module test();
reg [7:0]data[7:1];
initial
$readmemb("data.txt", data, 2);//起始地址为2(必须在1到7之间),则地址范围是2到7
endmodule
data.txt
00 //地址为-1
01 //地址为0
10 //地址为1
11 //地址为2, 从这里开始读取
@3
00 //地址为3
01 //地址为4
10 //地址为5
11 //地址为6
@0
00 //地址为1
00 //地址为2,覆盖了地址为2的原值11
11 //地址为3,覆盖了地址为3的原值00
00 //地址为4,覆盖了地址为4的原值01
data.txt
00 //地址为-1
01 //地址为0
10 //地址为1
11 //地址为2, 从这里开始读取
@3
00 //地址为3
01 //地址为4
10 //地址为5
11 //地址为6
11 //地址为7
11 //地址为8,当试图读取这个数时会提示超出范围(Too many data words read on line 11 of
//file并结束读取,因此后面不会覆盖
@0
00 //地址为1
00 //地址为2,
11 //地址为3,
00 //地址为4,