如何使用 TEXTIO 从文件初始化 RAM
使用初始值填充块 RAM 的一种方便方法是从 ASCII 文件中读取二进制或十六进制文字。这也是在 VHDL 中创建 ROM(只读存储器)的好方法。毕竟在 FPGA 中 RAM 和 ROM 是一回事,ROM 是你只能读取的 RAM。
本文中的示例将假定以下常量和 RAM 类型已在 VHDL 文件的声明区域的开头声明。
constant ram_depth : natural := 256; constant ram_width : natural := 32; type ram_type is array (0 to ram_depth - 1) of std_logic_vector(ram_width - 1 downto 0);
这篇博文是关于在 VHDL 中使用 TEXTIO 库的系列文章的一部分。在此处阅读其他文章:
使用 TEXTIO 在测试台中读取刺激文件
使用 TEXTIO 读取 BMP 文件位图图像
READLINE、LINE、HREAD、OREAD 和 BREAD
VHDL读写外部文件所需的子程序和类型位于TEXTIO
包裹。这个包是 std
的一部分 图书馆。标准库总是被加载;因此,我们不必使用 library
显式导入它 关键字。
我们可以简单地继续使用 TEXTIO
像这样在我们的 VHDL 文件头中打包:
use std.textio.all;
我们将 RAM 数据存储在一个 ASCII 文件中,其中一行文本对应一个内存插槽。要读取一行文本,我们使用 READLINE
TEXTIO
中的程序 包裹。该过程有两个参数,文件名作为常量输入,解析的文本行作为 inout
多变的。 READLINE
的原型声明 程序和LINE
取自 VHDL 标准规范的类型如下所示。
procedure READLINE (file F: TEXT; L: inout LINE); type LINE is access STRING; -- A LINE is a pointer -- to a STRING value.
虽然LINE
的类 READLINE
的原型声明中没有明确指定参数 ,它是一个变量,因为这是 inout
的默认类 参数。 LINE
type 只是一个对字符串的访问类型,一个指向动态分配的字符串对象的指针。
VHDL-2008 定义了 OREAD
, HREAD
, 和 BREAD
从 LINE
中提取八进制、十六进制和二进制值的过程 目的。读取八进制和十六进制值的方法非常相似,八进制值只是十六进制的子集。为简单起见,本文将跳过八进制读取,重点介绍如何从文本文件中读取十六进制和二进制值。
下面的代码显示了与我们相关的过程的定义,它们仅在 VHDL-2008 和更新版本中可用。 OREAD
和 HREAD
对于每种支持的输出类型,过程都有两种重载风格。可选的 GOOD
输出可用于检测读取错误,但是,无论是否使用此输出,大多数工具都会产生错误或警告。
procedure OREAD (L : inout LINE; VALUE : out STD_ULOGIC_VECTOR; GOOD : out BOOLEAN); procedure OREAD (L : inout LINE; VALUE : out STD_ULOGIC_VECTOR); procedure HREAD (L : inout LINE; VALUE : out STD_ULOGIC_VECTOR; GOOD : out BOOLEAN); procedure HREAD (L : inout LINE; VALUE : out STD_ULOGIC_VECTOR); alias BREAD is READ [LINE, STD_ULOGIC_VECTOR, BOOLEAN]; alias BREAD is READ [LINE, STD_ULOGIC_VECTOR];单击此处查看 TEXTIO 库中的输入过程定义
procedure READLINE (file F: TEXT; L: inout LINE); procedure READ (L: inout LINE; VALUE: out BIT; GOOD: out BOOLEAN); procedure READ (L: inout LINE; VALUE: out BIT); procedure READ (L: inout LINE; VALUE: out BIT_VECTOR; GOOD: out BOOLEAN); procedure READ (L: inout LINE; VALUE: out BIT_VECTOR); procedure READ (L: inout LINE; VALUE: out BOOLEAN; GOOD: out BOOLEAN); procedure READ (L: inout LINE; VALUE: out BOOLEAN); procedure READ (L: inout LINE; VALUE: out CHARACTER; GOOD: out BOOLEAN); procedure READ (L: inout LINE; VALUE: out CHARACTER); procedure READ (L: inout LINE; VALUE: out INTEGER; GOOD: out BOOLEAN); procedure READ (L: inout LINE; VALUE: out INTEGER); procedure READ (L: inout LINE; VALUE: out REAL; GOOD: out BOOLEAN); procedure READ (L: inout LINE; VALUE: out REAL); procedure READ (L: inout LINE; VALUE: out STRING; GOOD: out BOOLEAN); procedure READ (L: inout LINE; VALUE: out STRING); procedure READ (L: inout LINE; VALUE: out TIME; GOOD: out BOOLEAN); procedure READ (L: inout LINE; VALUE: out TIME); procedure SREAD (L: inout LINE; VALUE: out STRING; STRLEN: out NATURAL); alias STRING_READ is SREAD [LINE, STRING, NATURAL]; alias BREAD is READ [LINE, BIT_VECTOR, BOOLEAN]; alias BREAD is READ [LINE, BIT_VECTOR]; alias BINARY_READ is READ [LINE, BIT_VECTOR, BOOLEAN]; alias BINARY_READ is READ [LINE, BIT_VECTOR]; procedure OREAD (L: inout LINE; VALUE: out BIT_VECTOR; GOOD: out BOOLEAN); procedure OREAD (L: inout LINE; VALUE: out BIT_VECTOR); alias OCTAL_READ is OREAD [LINE, BIT_VECTOR, BOOLEAN]; alias OCTAL_READ is OREAD [LINE, BIT_VECTOR]; procedure HREAD (L: inout LINE; VALUE: out BIT_VECTOR; GOOD: out BOOLEAN); procedure HREAD (L: inout LINE; VALUE: out BIT_VECTOR); alias HEX_READ is HREAD [LINE, BIT_VECTOR, BOOLEAN]; alias HEX_READ is HREAD [LINE, BIT_VECTOR];单击此处查看 std_logic_1164 库中的输入过程定义
procedure READ (L : inout LINE; VALUE : out STD_ULOGIC; GOOD : out BOOLEAN); procedure READ (L : inout LINE; VALUE : out STD_ULOGIC); procedure READ (L : inout LINE; VALUE : out STD_ULOGIC_VECTOR; GOOD : out BOOLEAN); procedure READ (L : inout LINE; VALUE : out STD_ULOGIC_VECTOR); alias BREAD is READ [LINE, STD_ULOGIC_VECTOR, BOOLEAN]; alias BREAD is READ [LINE, STD_ULOGIC_VECTOR]; alias BINARY_READ is READ [LINE, STD_ULOGIC_VECTOR, BOOLEAN]; alias BINARY_READ is READ [LINE, STD_ULOGIC_VECTOR]; procedure OREAD (L : inout LINE; VALUE : out STD_ULOGIC_VECTOR; GOOD : out BOOLEAN); procedure OREAD (L : inout LINE; VALUE : out STD_ULOGIC_VECTOR); alias OCTAL_READ is OREAD [LINE, STD_ULOGIC_VECTOR, BOOLEAN]; alias OCTAL_READ is OREAD [LINE, STD_ULOGIC_VECTOR]; procedure HREAD (L : inout LINE; VALUE : out STD_ULOGIC_VECTOR; GOOD : out BOOLEAN); procedure HREAD (L : inout LINE; VALUE : out STD_ULOGIC_VECTOR); alias HEX_READ is HREAD [LINE, STD_ULOGIC_VECTOR, BOOLEAN]; alias HEX_READ is HREAD [LINE, STD_ULOGIC_VECTOR];
从文件中读取十六进制值
十六进制是描述 RAM 内容的方便格式,因为两个十六进制字符直接转换为一个字节,八位。每个字符描述一个半字节(半字节),文本文件中的每一行描述一个 RAM 插槽的内容。下面的清单显示了 ram_content_hex.txt 的摘录 文件。它已经填充了从十进制 1 到 256 的示例值,写为十六进制。
12–255256 | 00000001 00000002 ... 000000FF 00000100 |
为了从文本文件中加载数据,我们使用在 ram_type
下声明的不纯函数 ,但在 RAM 信号声明之上。下面的代码显示了 init_ram_hex
从文本文件中读取数据并将其返回为 ram_type
的函数 对象。
impure function init_ram_hex return ram_type is file text_file : text open read_mode is "ram_content_hex.txt"; variable text_line : line; variable ram_content : ram_type; begin for i in 0 to ram_depth - 1 loop readline(text_file, text_line); hread(text_line, ram_content(i)); end loop; return ram_content; end function;
readline
for循环内的过程每次读取一行文本并将其分配给text_line
多变的。此对象的类型为 line
,它是对字符串对象的访问类型,是指向动态分配的字符串的指针。在下一行,hread
过程从 line
读取字符串 对象并将其转换为 std_ulogic_vector
.这个类型可以直接赋值给std_logic_vector
每个 RAM 单元都是由它构成的。
最后,我们在调用 init_ram_hex
时声明 RAM 信号 为其提供初始值的函数:
signal ram_hex : ram_type := init_ram_hex;
VHDL-2002 和 VHDL-93 中的 HREAD
不幸的是,HREAD
程序仅在 VHDL-2008 中可用。在所有以前版本的 VHDL 中,标准 READ
必须改用程序。 READ
过程重载了许多不同的输出类型,但没有读取十六进制值的选项。
让我们编写一个自定义算法,用于将十六进制 ASCII 字符转换为 VHDL std_logic_vector
.首先,我们需要从text_line
中逐一读取字符 对象,然后我们解码它们的值并将它们分配给 RAM 槽向量的正确切片。下面的代码显示了 init_ram_hex
的等效实现 也适用于旧版 VHDL 版本的函数。
impure function init_ram_hex return ram_type is file text_file : text open read_mode is "ram_content_hex.txt"; variable text_line : line; variable ram_content : ram_type; variable c : character; variable offset : integer; variable hex_val : std_logic_vector(3 downto 0); begin for i in 0 to ram_depth - 1 loop readline(text_file, text_line); offset := 0; while offset < ram_content(i)'high loop read(text_line, c); case c is when '0' => hex_val := "0000"; when '1' => hex_val := "0001"; when '2' => hex_val := "0010"; when '3' => hex_val := "0011"; when '4' => hex_val := "0100"; when '5' => hex_val := "0101"; when '6' => hex_val := "0110"; when '7' => hex_val := "0111"; when '8' => hex_val := "1000"; when '9' => hex_val := "1001"; when 'A' | 'a' => hex_val := "1010"; when 'B' | 'b' => hex_val := "1011"; when 'C' | 'c' => hex_val := "1100"; when 'D' | 'd' => hex_val := "1101"; when 'E' | 'e' => hex_val := "1110"; when 'F' | 'f' => hex_val := "1111"; when others => hex_val := "XXXX"; assert false report "Found non-hex character '" & c & "'"; end case; ram_content(i)(ram_content(i)'high - offset downto ram_content(i)'high - offset - 3) := hex_val; offset := offset + 4; end loop; end loop; return ram_content; end function;
该算法在查看每个字符的同时简单地遍历每一行,将其转换为正确的二进制值。如果遇到不在 0x0-0xF 范围内的字符,则会在 when others
中引发断言失败 分支。 offset
变量控制每个存储单元内的切片位置,将解码后的值分配给。
您可能会问自己,为什么我们不创建自定义 hread
过程而不是在 init_ram_hex
内对其进行编码 功能?这样我们就不必更改 init_ram_hex
函数,我们只需使用我们自定义的 hread
程序代替缺少的标准程序。
这适用于大多数模拟器和一些合成器,如 Lattice iCEcube2,但它不会在 Xilinx Vivado 中合成。下面的错误信息清楚地说明了问题所在。
在 Vivado 中:
[Synth 8-27] 不支持“line”类型的过程参数 [init_ram_tb.vhd:15]
procedure hread(l: inout line; value: out std_logic_vector) is variable c : character; variable ok : boolean; variable i : integer := 0; variable hex_val : std_logic_vector(3 downto 0); begin while i < value'high loop read(l, c); case c is when '0' => hex_val := "0000"; when '1' => hex_val := "0001"; when '2' => hex_val := "0010"; when '3' => hex_val := "0011"; when '4' => hex_val := "0100"; when '5' => hex_val := "0101"; when '6' => hex_val := "0110"; when '7' => hex_val := "0111"; when '8' => hex_val := "1000"; when '9' => hex_val := "1001"; when 'A' | 'a' => hex_val := "1010"; when 'B' | 'b' => hex_val := "1011"; when 'C' | 'c' => hex_val := "1100"; when 'D' | 'd' => hex_val := "1101"; when 'E' | 'e' => hex_val := "1110"; when 'F' | 'f' => hex_val := "1111"; when others => hex_val := "XXXX"; assert false report "Found non-hex character '" & c & "'"; end case; value(value'high - i downto value'high - i - 3) := hex_val; i := i + 4; end loop; end procedure;
从文件中读取二进制值
如果 RAM 宽度不是 8 的倍数,您可能希望将 RAM 值存储为二进制文字而不是十六进制字符。下面的清单显示的内容与以前相同,但仅使用字符 0
和 1
.
12–255256 | 00000000000000000000000000000001 00000000000000000000000000000010 ... 00000000000000000000000011111111 00000000000000000000000100000000 |
下面显示的算法用于从文件中读取二进制值。它类似于读取十六进制,但在 VHDL-2008 中您应该使用 BREAD
过程调用而不是 HREAD
.它将一个 ASCII 字符转换为单个 std_ulogic
值,隐式转换为 std_logic
.
impure function init_ram_bin return ram_type is file text_file : text open read_mode is "ram_content_bin.txt"; variable text_line : line; variable ram_content : ram_type; begin for i in 0 to ram_depth - 1 loop readline(text_file, text_line); bread(text_line, ram_content(i)); end loop; return ram_content; end function;
最后,我们通过调用新的不纯函数来初始化 RAM 信号,如下面的代码所示。
signal ram_bin : ram_type := init_ram_bin;
VHDL-2002 和 VHDL-93 中的 BREAD
我们可以通过调用 READ
轻松地将我们的代码移植到旧版 VHDL 版本 而不是 BREAD
.下面的 VHDL 标准摘录显示了 READ
的原型 我们有兴趣使用。
procedure READ (L: inout LINE; VALUE: out BIT);
READ
输出 std_ulogic
的过程 在 VHDL-2008 之前不存在,因此我们必须使用 bit
TEXTIO
的版本 图书馆。幸运的是,这种类型可以很容易地转换为 std_logic
通过使用标准 To_StdLogicVector
功能。
init_ram_bin
的实现 如下所示适用于 VHDL-2002 和 VHDL-93。
impure function init_ram_bin return ram_type is file text_file : text open read_mode is "ram_content_bin.txt"; variable text_line : line; variable ram_content : ram_type; variable bv : bit_vector(ram_content(0)'range); begin for i in 0 to ram_depth - 1 loop readline(text_file, text_line); read(text_line, bv); ram_content(i) := To_StdLogicVector(bv); end loop; return ram_content; end function;
IEEE std_logic_1164 库的向后移植
更改旧 VHDL 版本代码的替代方法是使用 std_logic_1164_additions 第三方包。通过下载这个库并将其添加到您的项目中,您将能够在 VHDL-2002 和 VHDL-93 中使用新程序。当然,那么您将导入更多内容,并且您的代码将始终依赖于这个包。
VHDL