在
第一篇文章的续篇中,我想以python为例演示使用FPGA(FPGA)的示例。 本文将更详细地介绍测试方面。 如果
MyHDL框架允许人们使用熟悉的语法和生态系统来研究python,那么有经验的FPGA开发人员将不了解使用python的含义。 MyHDL和Verilog的硬件描述范例相似,选择特定语言取决于习惯和口味。 Verilog / VHDL代表了使用这些语言编写固件很长时间的事实,并且事实上,它们是描述数字设备的标准。 作为该领域的新手,Python可以在编写测试环境中竞争。 FPGA开发人员的大部分时间都花在了测试他们的设计上。 接下来,我想以一个示例来演示如何使用MyHDL在python中完成此操作。
假设有一个任务描述一个与FPGA上的存储器一起工作的器件。 为简单起见,我将使用通过并行接口(而不是通过串行接口,例如I2C)与其他设备通信的内存。 考虑到这样的微电路并非总是实用的,因为需要与它们一起工作的许多引脚;另一方面,提供了更快和更容易的信息交换。 例如,家用1645RU1U及其类似物。

模块说明
记录如下所示:FPGA提供16位单元地址,8位数据,并生成写信号WE(写使能)。 由于OE(输出使能)和CE(芯片使能)始终处于启用状态,因此在更改单元地址时会发生读取。 可以从一个特定的adr_start地址开始(记录在adr_write信号的前沿)开始,并在一个行中的多个单元中依次进行写入和读取,也可以在任意地址处进行一个单元(随机访问)。
在MyHDL上,代码如下所示(写入和读取信号采用反向逻辑):
from myhdl import * @block def ram_driver(data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we):
如果使用以下功能转换为Verilog:
def convert(hdl): data_memory = TristateSignal(intbv(0)[8:]) data_in = Signal(intbv(0)[8:]) data_out = Signal(intbv(0)[8:]) adr = Signal(intbv(0)[16:]) adr_start = Signal(intbv(0)[16:]) adr_write = Signal(bool(0)) read, write, we = [Signal(bool(1)) for i in range(3)] inst = ram_driver(data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we) inst.convert(hdl=hdl) convert(hdl='Verilog')
然后我们得到以下内容:
`timescale 1ns/10ps module ram_driver ( data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we ); input [7:0] data_in; output [7:0] data_out; reg [7:0] data_out; output [15:0] adr; reg [15:0] adr; input [15:0] adr_start; input adr_write; inout [7:0] data_memory; wire [7:0] data_memory; input read; input write; output we; reg we; reg [7:0] mem_z; assign data_memory = mem_z; always @(write) begin: RAM_DRIVER_WRITE_DATA if ((!write)) begin mem_z <= data_in; we <= 0; end else begin mem_z <= 'bz; data_out <= data_memory; we <= 1; end end always @(posedge adr_write, posedge write, negedge read) begin: RAM_DRIVER_WRITE_START_ADR if (adr_write) begin adr <= adr_start; end else begin adr <= (adr + 1); end end endmodule
无需将项目转换为Verilog即可进行仿真;升级FPGA将需要此步骤。
造型
在逻辑描述之后,应验证项目。 您可以限制自己,例如,模拟输入影响并在时间图中查看模块的响应。 但是使用此选项,更难预测模块与内存芯片的交互。 因此,要完全验证创建的设备的运行,您需要创建一个内存模型并测试这两个设备之间的交互。
由于工作是在python中进行的,因此给定类型的字典(字典)建议将其用于内存模型。 数据以{key:value}的形式存储,在这种情况下为{address:data}。
memory = { 0: 123, 1: 456, 2: 789 } memory[0] >> 123 memory[1] >> 456
出于相同的目的,列表数据类型是合适的,其中每个元素都有其自己的坐标,以指示元素在列表中的位置:
memory = [123, 456, 789] memory[0] >> 123 memory[1] >> 456
考虑到更大的可见性,使用字典来模拟内存似乎是可取的。
测试外壳的描述(在文件test_seq_access.py中)从信号的声明,初始状态的初始化以及将它们扔入上述内存驱动器功能开始:
@block def testbench(): data_memory = TristateSignal(intbv(0)[8:]) data_in = Signal(intbv(0)[8:]) data_out = Signal(intbv(0)[8:]) adr = Signal(intbv(0)[16:]) adr_start = Signal(intbv(20)[16:]) adr_write = Signal(bool(0)) read, write, we = [Signal(bool(1)) for i in range(3)] ram = ram_driver(data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we)
下面介绍内存模型。 初始化初始状态,默认情况下,内存中会填充零值。 将内存模型限制为128个单元:
memory = {i: intbv(0) for i in range(128)}
并描述内存的行为:当WE处于低电平状态时,将该行中的值写入相应的内存地址,否则模型给出给定地址处的值:
mem_z = data_memory.driver() @always_comb def access(): if not we: memory[int(adr.val)] = data_memory.val if we: data_out.next = memory[int(adr.val)] mem_z.next = None
然后,在同一功能中,您可以描述输入信号的行为(对于顺序写/读的情况):记录起始地址→记录8个信息单元→记录起始地址→读取8个记录的信息单元。
@instance def stimul(): init_adr = random.randint(0, 50) # yield delay(100) write.next = 1 adr_write.next = 1 adr_start.next = init_adr # yield delay(100) adr_write.next = 0 yield delay(100) for i in range(8): # 8 write.next = 0 data_in.next = random.randint(0, 100) yield delay(100) write.next = 1 yield delay(100) adr_start.next = init_adr # adr_write.next = 1 yield delay(100) adr_write.next = 0 yield delay(100) for i in range(8): # read.next = 0 yield delay(100) read.next = 1 yield delay(100) raise StopSimulation return stimul, ram, access
运行模拟:
tb = testbench() tb.config_sim(trace=True) tb.run_sim()
启动程序后,在工作文件夹中生成testbench_seq_access.vcd文件,在gtkwave中将其打开:
gtkwave testbench_seq_access.vcd
我们看到了图片:

已成功读取记录的信息。
您可以通过将以下代码添加到testbench来查看内存的内容:
for key, value in memory.items(): print('adr:{}'.format(key), 'data:{}'.format(value))
控制台中将显示以下内容:

测试中
此后,您可以使用数量更多的可写/可读单元格进行一些自动化测试。 为此,将几个测试周期和伪字典添加到testbench,在其中添加了书面和可读信息以及assert结构,如果两个字典不相等,则会导致错误:
@instance def stimul(): for time in range(100): temp_mem_write = {} temp_mem_read = {} init_adr = random.randint(0, 50) yield delay(100) write.next = 1 adr_write.next = 1 adr_start.next = init_adr yield delay(100) adr_write.next = 0 yield delay(100) for i in range(64): write.next = 0 data_in.next = random.randint(0, 100) temp_mem_write[i] = int(data_in.next) yield delay(100) write.next = 1 yield delay(100) adr_start.next = init_adr adr_write.next = 1 yield delay(100) adr_write.next = 0 yield delay(100) for i in range(64): read.next = 0 temp_mem_read[i] = int(data_out.val) yield delay(100) read.next = 1 yield delay(100) assert temp_mem_write == temp_mem_read, " " for key, value in memory.items(): print('adr:{}'.format(key), 'data:{}'.format(value)) raise StopSimulation return stimul, ram, access
接下来,您可以创建第二个测试平台来测试随机访问模式下的操作:test_random_access.py。
第二个测试的想法是相似的:我们在随机地址处写入随机信息,并向temp_mem_write字典中添加一对{address:data}。 然后,我们遍历此字典中的地址,并从内存中读取信息,并将其输入到temp_mem_read字典中。 最后,assert构造检查两个字典的内容。
import random from myhdl import * from ram_driver import ram_driver @block def testbench_random_access(): data_memory = TristateSignal(intbv(0)[8:]) data_in = Signal(intbv(0)[8:]) data_out = Signal(intbv(0)[8:]) adr = Signal(intbv(0)[16:]) adr_start = Signal(intbv(20)[16:]) adr_write = Signal(bool(0)) read, write, we = [Signal(bool(1)) for i in range(3)] ram = ram_driver(data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we) memory ={i:intbv(0) for i in range(128)} mem_z = data_memory.driver() @always_comb def access(): if not we: memory[int(adr.val)] = data_memory.val if we: data_out.next = memory[int(adr.val)] mem_z.next = None @instance def stimul(): for time in range(10): temp_mem_write = {} temp_mem_read = {} yield delay(100) for i in range(64): write.next = 1 adr_write.next = 1 adr_start.next = random.randint(0, 126) yield delay(100) adr_write.next = 0 yield delay(100) write.next = 0 data_in.next = random.randint(0, 100) temp_mem_write[int(adr_start.val)] = int(data_in.next) yield delay(100) write.next = 1 yield delay(100) for key in temp_mem_write.keys(): adr_start.next = key adr_write.next = 1 yield delay(100) adr_write.next = 0 yield delay(100) read.next = 0 temp_mem_read[key] = int(data_out.val) yield delay(100) read.next = 1 yield delay(100) assert temp_mem_write == temp_mem_read, ' random access' raise StopSimulation return stimul, ram, access tb = testbench_random_access() tb.config_sim(trace=True) tb.run_sim()
Python有几个框架可以自动执行测试。 为了简单起见,我将使用pytest,它必须从pip安装:
pip3 install pytest
当从控制台启动“ pysest”命令时,框架将在工作文件夹中查找并执行所有文件名中带有“ test_ *”的文件。

测试成功完成。 我会在设备说明中故意犯一个错误:
@block def ram_driver(data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we): mem_z = data_memory.driver() @always(adr_write.posedge, write.posedge, read.negedge) def write_start_adr(): if adr_write: adr.next = adr_start else: adr.next = adr + 1 @always(write) def write_data(): if not write: mem_z.next = data_in we.next = 1 # , else: mem_z.next = None data_out.next = data_memory we.next = 1
我运行测试:

如预期的那样,在两个测试中,都考虑了初始信息(零),即未记录新信息。
结论
将python与myHDL结合使用,可以使您自动为FPGA开发的固件进行测试,并使用python编程语言的丰富功能创建几乎任何测试环境。
本文认为:
- 创建一个与内存兼容的模块;
- 创建一个内存模型;
- 测试案例创建;
- 使用pytest框架进行自动化测试。