如何使用 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