FPGA逻辑分析仪
作为工程专业的学生,我们经常发现自己正在使用各种通信协议或需要特定的波形。但是,可以输出特定功能或分析通讯的实验室设备通常很昂贵。因此,我们的项目为学生提供了机会,使他们能够以很少的成本购买这种工具。通过使用fpga,我们能够实时解码通信,同时还允许来自HPS的软件配置。
概述
该项目包括两个主要部分:FPGA和HPS。FPGA负责在可变数量的数字输入通道上采样数据。FPGA在硬件上执行SPI和I2C的解码,并且还能够记录信号的上升沿和下降沿以重建原始波形。HPS负责运行基于python的GUI,该GUI通过从SRAM和环形缓冲区读取数据来绘制采样数据。GUI还允许对fpga中使用的分析器模块进行软件配置。
高级设计
项目原理
该项目的灵感来自模拟发现产品,以及我们对能够分析数字逻辑和输出波形的多功能工具的渴望。在当今时代,准确而可靠的工具通常非常昂贵。因此,在需要进行许多设置的教室和实验室中,这种昂贵的工具不是可行的选择。因此,学生常常没有适当地解码逻辑的手段,这可能会导致调试过程中的挫败感。因此,我们的小组希望创建一种工具,该工具对于实验室而言既具有成本效益,又对学生有用。我们想出的解决方案是使用SoC / FPGA来实现这种工具。在设计项目时,我们做出的决定之一是使解码块具有可标记性。这意味着我们可以选择在硬件编译之前需要多少块。这样一来,无论学生使用什么SoC / FPGA系统,他们都可以使用我们的项目,因为他们可以自定义模块的数量,以使用系统所拥有的逻辑空间。对于该项目的GUI部分,我们选择对应用程序进行x转发。这使学生可以从自己的设备运行我们的应用程序,而无需安装python或GUI中使用的其他各种软件包。对于该项目的GUI部分,我们选择对应用程序进行x转发。这使学生可以从自己的设备运行我们的应用程序,而无需安装python或GUI中使用的其他各种软件包。对于该项目的GUI部分,我们选择对应用程序进行x转发。这使学生可以从自己的设备运行我们的应用程序,而无需安装python或GUI中使用的其他各种软件包。
逻辑结构
该项目分为三个主要部分:硬件逻辑分析器/任意函数生成器,软件GUI和PIC32,其生成一系列测试输入。硬件读取和解码数字输入以及输出任意功能,并由HPS上以Python运行的GUI控制,该GUI可以配置硬件并向用户显示输出。为了可视化有用的输入,PIC32生成PWM,I2C和SPI输入。
硬件/软件权衡
我们在设计中进行了各种权衡。我们认为,最大的地方应该是总线协议解码发生的地方。我们选择在硬件中对SPI和I2C进行解码,因为可以通过将SPI事务编码为数据来实现节省存储空间。这确实增加了设计的复杂性,因为它更容易在软件中实现。此外,我们失去了灵活性,因为现在新的总线协议支持需要硬件修改,这比软件更改要大得多。
此外,我们选择使用Python作为GUI语言,而不是C语言。尽管这确实需要花费更多的精力来设置软件包(甚至需要我们完全切换操作系统),但它极大地提高了我们迭代GUI的速度。设计。我们还为Python付出了性能成本,因为它是一种解释型语言,它将比C慢得多。
程序/硬件设计
计划详情
图形用户界面
为了处理解码数据的显示,我们选择实现在HPS上运行的基于python的GUI。之所以做出此决定,是因为我们将VGA输出的DAC用于任意函数发生器。因此,不能通过FPGA处理GUI。此外,由于我们小组的成员已经具有使用它的经验,因此我们选择将GUI作为python集成的tkinter库的基础。GUI本身是观察已解码数据以及配置硬件交叉开关和解码块的主要方法。下图显示了我们GUI的主窗口。
设定档
GUI的主要功能之一是能够从软件重新配置硬件交叉开关的功能。这使我们能够重新分配引脚输入,解码器模块功能和触发条件,而无需重新编译硬件本身。下图显示了解码器块配置菜单之一的示例。可以看出,第一行允许重新配置解码块的功能。这使我们能够更改硬件正在动态解码的协议。这样,用户无需硬件重新编译就可以从解码GPIO切换到SPI或I2C。此外,我们还可以即时设置触发条件,从而可以查看尝试在不同事件上触发的结果。
实际上,从软件配置fpga分为两个阶段。这样做的原因是,在将更改发送到fpga之前,一次可以配置多个块。第一步是在上述菜单中选择所需的设置。选择所需的设置后,选择“保存”按钮将在GUI类变量中设置相应的位。这显示在下面的代码中。
def save(decode_type, decoder_num, pin1, pin2, pin3, pin4, trig, trigCond, trigMask):
#We call .get() after each variable to obtain their integer forms
#This is because tkinter wdigets use tkinter variables which must be converted
gui.block_cfg[decoder_num] = decode_type.get()
print(gui.block_cfg[0])
if (decoder_num == 0):
gui.block1_pins[0] = pin1.get();
gui.block1_pins[1] = pin2.get();
gui.block1_pins[2] = pin3.get();
gui.block1_pins[3] = pin4.get();
gui.triggers [0] = trig.get();
gui.trig_cond = int(trigCond.get(),16);
gui.trig_mask = int(trigMask.get(),16);
#trig_cond and trig_mask are tk.StringVars and must be casted to int using base 16
保存功能与每个解码器块菜单相关,并且对于该块是唯一的。相应地配置了块之后,按在完整GUI图片中看到的蓝色箭头按钮会将每个块的配置发送到fpga。这样做的代码如下所示。由于set_pin_configs函数的输入是一个数组,因此我们将每个模块的引脚配置连接到一个数组中,然后将其发送到fpga。
def set_configs():
pins_config = []
pins_config.extend(gui.block1_pins)
pins_config.extend(gui.block2_pins)
pins_config.extend(gui.block3_pins)
pins_config.extend(gui.block4_pins)
h.set_pin_configs(pins_config)
h.set_trigger_ (gui.triggers [0],gui.triggers [1],
gui.triggers [2],gui.triggers [3],
gui.trig_mask,gui.trig_cond)
h.set_block_configs(gui.block_cfg [0],gui.block_cfg [1],gui.block_cfg [2],
gui.block_cfg [3],gui.block_cfg [4])
绘图
可以说,GUI的最重要特征是能够对解码后的数据进行图形处理。为了支持此功能,我们使用了适用于python的matplotlib库。每个解码器模块最多可分配4个子图,每个引脚一个。为了能够在从SRAM和环形缓冲区读取采样数据时更新每个图,我们使用matplotlib的动画功能。这使我们可以在设定的时间间隔内为每个系列的子图调用绘图功能。要实际从硬件读取数据,我们使用自定义的硬件/软件界面。下面显示了如何完成此操作的示例。可以看出,我们使用接口的get_trigger_status()函数来读取32位触发状态值。该值的MSB告诉我们是否已触发。接下来的2位描述了触发了哪些解码块,最后下面的29位是SRAM的触发地址。如果已触发,则从触发地址开始从SRAM读取数据,并继续进行500个采样。如果未触发,则从SRAM读取地址0到500。
trig_status = h.get_trigger_status()
trigger = bin(trig_status[0])
trigger_int = int(trigger,2)
check = int(trigger,2)
if(check != 0): #if it is 0, we did not trigger, and so the return value is 0
status = bitslice(trigger_int, 31, 31)
status_block = bitslice(trigger_int, 29, 30)
trig_addr = bitslice(trigger_int, 0, 28)
else:
status = 0
status_block = 0
trig_addr = 0
if (status and (status_block == 0)):
samples = h.read_samples(trig_addr, 500)
else:
samples = h.read_samples(0, 500)
还需要注意的是我们暂停和开始绘制采样数据的能力。这些操作绑定到在整体GUI图片中看到的开始和暂停按钮。实现这一点非常简单,因为我们使用了全局暂停变量,然后将其封装在绘图函数中。单击“暂停”或“开始”按钮将更改此全局值。此外,通过使用内置的matplotlib工具栏,用户可以放大单个图,保存每个图以供将来分析,还可以调整每个图的设置,而无需重新启动GUI。函数生成
GUI的最后一个方面是能够通过绘制波形来创建波形波形。尽管在许多情况下不一定有用,但这使我们能够确认函数发生器是否正常工作。在我们报告的结果部分中可以看到一个例子。按下GUI右上角的三个图形按钮之一,可以绘制要通过三个函数发生器之一显示的波形。为了绘制波形,我们使用pygame库。该库允许我们绘制一条线,当完成绘制时,将返回其坐标。然后,这些结果将在我们的硬件/软件界面中使用,以告诉函数生成器要创建什么。如果我们绘制一个x值的y值超过一个y的波形,则将较大的y值用于函数生成。
GUI性能和X转发
GUI的整体性能仍然需要做一些工作,其中有几个因素在起作用。第一个因素是所使用的python库。为了绘制数据图,我们选择使用matplotlib库。但是,这是一个相当繁重的库,这给处理器增加了很大的工作量,尤其是当我们需要一次绘制多个图时。由于板载处理器是一个简单的微控制器,因此通常会将MCU推向极限。这可以从以下事实中看出:仅启动GUI时,我们的处理器使用率就跃升至近100%。
GUI的性能问题中的另一个重要因素是x转发。如前所述,我们选择使用x-forwarding来使我们项目的用户能够运行我们的GUI和硬件设置,而无需在自己的计算机上安装python或其软件包。但是,此方法的缺点是,由于X11固有的设计缺陷,x-forwading的性能下降幅度很大。X11不会将屏幕发送到用户计算机,而是将显示指令发送到本地X11服务器,然后该服务器用来在用户本地计算机上重新创建屏幕。这必须在每次更改/刷新显示时发生。由于本地计算机不知道需要更新什么,并且远程服务器也不知道客户端需要什么,这导致发送大量冗余数据。在我们的GUI使用的动画图形或图像的情况下尤其如此。实际上,在测试中,我们能够发现图形更新和暂停之间的性能显着下降。
展望未来,我们可能会寻找matplotlib的替代方案。提高性能的潜在解决方案之一是在滚动文本框中以字符串形式输出解码后的通信结果。这仍将使用户了解通过其通信线路发送的消息,同时消除了matplotlib的开销。最有可能的是,我们会根据用户的系统规格为用户提供选择使用哪种选项。
硬件细节
分析仪模块设计
分析器模块是模块化和可配置的硬件,可以执行数字输入的所有采集和解码。它具有4个输入,并能够解码SPI和I2C,并具有模块化接口,可轻松支持更多协议。此外,它能够记录引脚上的任何上升沿和下降沿,从而可以完全重建所有数字信号。该块可在运行时重新配置,这意味着GUI可以修改交叉开关连接,解码类型和触发条件,而无需重新编译硬件。
解码
该模块支持解码I2C或SPI,或仅记录每个输入的上升沿和下降沿以完全重建给定的数字信号。每个解码器从输入交叉开关接收输入,该输入交叉开关能够将四个外部输入中的任何一个重新映射到解码器的引脚。解码器需要知道哪个引脚是时钟,数据或特定协议的任何其他重要信号。输入交叉开关允许用户以他们喜欢的任何方式插入信号,并且使用软件接口,他们可以让硬件知道他们如何连接信号,而不是强迫用户为特定信号使用特定的引脚。
解码SPI
SPI解码器相对简单。当CS线被拉低时,交易开始。在CS较低的情况下,解码器将在SCK的每个上升沿从MOSI和MISO移入新值。当CS变高时,如果解码器感觉到整个事务已发生,则SPI值将被发送到分析器模块。这意味着解码器将丢弃未发送完整8位的事务,以避免误识别格式错误的事务。下面显示了ModelSim中的一个示例。
上面的ModelSim快照显示了在两个不同的SPI事务下解码器的行为。第一个事务在MOSI和MISO线上均显示0xFF。我们可以看到,内部“ miso_value”和“ mosi_value”信号随着SCK的每个上升沿而更新。当我们到达事务结束时,这两个信号都代表通过总线发送的值。当CS拉高时,两个miso和mosi值将打包到一个称为“ acquire_data”的结果中,并发出“ acquire_val”信号,以使分析器模块知道它应获取此解码器的数据。同样,另一个SPI事务将紧随其后,这一次是在MOSI和MISO线上发送0xAA和0x55。就像之前的交易一样,“ mosi_value”和“ miso_value”的值会随着位的到达而更新,
解码I2C
与SPI相比,解码I2C的简单性略差一些。I2C事务包括四个主要部分:START条件,8位数据,ACK / NACK(可能是STOP条件)。该解码器跟踪开始是否是重复开始,8位数据,是否存在NACK或ACK以及是否声明了STOP。
以上来自ModelSim的波形显示了一个示例I2C发送0xFF并带有起始和ACK的示例。随着SCL的上升沿在总线上计时新值,我们可以看到“ current_byte_id”从0增加到7。在整个事务处理过程中,将缓慢构造字节值,从而将0xFF值返回给分析器块。从朝向图片按钮的信号中,我们可以看到在各个位置确定了START,ACK和STOP条件。
触发方式
触发单元可以被配置为在许多不同的事件上触发。它可以在任何输入的任何上升沿或下降沿上触发,或在其中一个解码器中屏蔽了交易值。例如,这意味着可以在以下情况下触发:
- 在任何上升沿
- 在引脚1的下降沿或引脚2和3的上升沿
- 通过SPI发送0x42时
- 当I2C事务的前四位为1时
由于触发器单元支持掩码和条件,因此潜在触发器的集合非常大。另外,由于触发单元的配置与总体块配置不同,因此有可能解码一个协议并在上升/下降沿或什至在另一协议上触发。
分析仪团块设计
我们的设计允许冲压出许多分析仪模块。实施该模块化设计是为了使我们的设计能够在各种硬件平台上运行,并且解码器通道的数量能够根据目标设备上的可用硬件进行扩展。我们将分析器块的分组称为“块”,下图试图说明该模块的各个组件。
可变数量的分析器模块都连接到循环仲裁器。每个分析器模块都试图将样本写入共享的SRAM。该仲裁程序来自康奈尔大学ECE 4750和ECE 5745课程提供的vc库。仲裁器接收请求的位向量,这些请求是请求访问共享资源的实体,在这种情况下,共享资源是共享的SRAM。这些块仅在内部缓冲区中有等待写入的数据时才断言其请求。循环仲裁器将以循环方式接受请求,尝试尽可能公平,同时确保在给定的周期内仅授予一个分析器块写入SRAM的权限。
延迟不敏感的通讯
由于我们的分析器模块将在不可知的时间产生结果(因为数据直接对应于外部输入),因此我们认为可以容忍数据快速爆发的对延迟不敏感的接口是最合适的。所有分析器模块共享一个公共SRAM,并将其样本数据写入其中。这是为了确保最大的内存利用率(划分为多个SRAM可能非常浪费)并降低软件复杂性。下图显示了我们如何构造接口。
对于每个环形缓冲区,相应的“环形缓冲区管理器”将跟踪其相关的元数据。每个环形缓冲区都有一个读指针和一个写指针,其中读指针描述读取器在缓冲区中的位置,写指针描述写入器在哪里。如果写指针在读指针后面,则有写空间。如果写指针位于读指针,则缓冲区已满,我们无法写。读指针有类似的逻辑。如果读取指针在写入指针的前面,那么我们可以读取,但是如果读取指针在读取指针的后面,则缓冲区为空,我们无法读取。环形缓冲区管理器的角色是将环形缓冲区伪装成无限的内存字符串,以方便读取器和写入器逻辑使用。
函数发生器设计
函数发生器实现使用板载3通道DAC,该DAC用于驱动VGA信号的R,G和B分量。相反,我们将每个通道用作8位DAC通道以生成任意功能。每个通道都有一个SRAM,其中包含要输出的点。SRAM由软件控制,每个函数发生器周期都从SRAM中读取一个新样本,并输出到DAC上。函数发生器还具有一个预分频器,可用于调整从SRAM读取样本的速率。这样一来,用户就不会锁定FPGA时钟的采样率,并且他们可以选择任意的预分频器值来减慢时钟速度。SRAM可通过软件编写,并且可通过GUI轻松控制。
PIC32实际测试
为了测试逻辑分析仪的解码能力,我们决定最好使用PIC32生成真实世界的波形。感谢Bruce教授,我们能够使用ECE4760的PIC32板和PIC微型棒使该项目的这一部分相对简单。为了测试正确的功能,我们需要各种GPIO信号,SPI和I2C的波形。PIC输出了4个GPIO信号,所有占空比和周期都不同。对于SPI,PIC充当主设备,并反复从0-255传输值并减慢它的速度。对于I2C协议,由于在使用PIC32上的I2C外设时遇到了一些问题,因此最终导致了特定值的位撞击。
我们使用以下代码将PIC32转换为从模式:
// Open SPI Channel 2 as master (@500 kHz) 8 bit mode
SpiChnOpen(spiChn, SPI_OPEN_ON | SPI_OPEN_MODE8 | SPI_OPEN_MSTEN | SPI_OPEN_CKE_REV , spiClkDiv);
// SDO2 (MOSI) is in PPS output group 2, could be connected to RB5 which is pin 14
PPSOutput(2, RPB5, SDO2);
PPSInput(3, SDI2, RPB13); // RBP13 --> SDI2
PPSInput(3, SS2, RPA3); // RPA3 --> Chip select
SPI的中断服务程序代码:
// CS low to start transaction
mPORTBClearBits(BIT_4); // start transaction
// test for ready
while (TxBufFullSPI2());
// write to spi2
WriteSPI2(count++); //send values 0-255
// test for done
while (SPI2STATbits.SPIBUSY); // wait for end of transaction
// CS high to end transaction
mPORTBSetBits(BIT_4);
结果
逻辑分析仪采样
在最终设计中,我们创建了一个16通道数字逻辑分析仪(可以轻松添加更多通道)。每个样本都具有以下内容之一:4个输入中的每个输入的上升/下降沿,一个SPI事务,一个I2C事务。样品带有时间戳,并标有采样时间和采样块。我们的46位时间戳没有溢出问题,我们可以非常准确地跟踪采样时间。在此实现中,总采样位宽为64位。但是,硬件解码可以节省大量数据。例如,如果我们使用四个连续采样通道以20 MHz对SPI进行采样,则我们将以(20 * 4)100 Mbps的速率产生数据。但是,如果我们以硬件解码SPI事务,并假设平均SPI通道最多在50%的时间产生事务,
我们已经能够记录4个GPIO通道,1个SPI通道和1个I2C,其中每个GPIO通道以大约500 kHz的频率切换,并且SPI和I2C通道的传输速度与PIC32可以传输的速度一样快(@ 20Mhz和100kHz,分别)。有关此结果的屏幕截图,请参见视频和GUI部分。
任意函数发生器
我们的设计还支持包含3通道任意函数发生器。有一些预置模式,例如三角波和方波,但是我们还包括绘制波形并在DAC输出上重新创建波形的功能。如下所示。绘制的波形在顶部,相应的输出显示在其下方。
结论
期望与现实
我们对该项目的结果感到非常满意。尽管我们能够在HPS上运行一个功能齐全的GUI,但由于X-Forwarding速度较慢,尤其是嵌入式HPS内核的计算能力相对较弱,我们不得不做出一些妥协。我们不能使用许多我们想要的图形库,并且GUI的响应时间比我们期望的要少。但是总的来说,我们对该项目感到非常满意,并认为该项目完全符合我们最初设定的最初目标。
法律和知识产权注意事项
为了产生这个项目,我们使用了Altera通过其Quartus IDE提供的各种FPGA IP。我们不打算分发或出售此软件或硬件。除非在文件头或此报告中另有说明,否则所有其他代码都是我们自己的。我们使用了ECE 4750中发布的一些库代码,并引用了此代码的用法。但是,由于我们尚未获得将其公开发布的明确许可,因此我们从公开发布中忽略了这一点。
附录
小组批准此报告以将其包含在课程网站中。该小组批准将视频收录到课程youtube频道中。
Verilog代码
在下面可以找到我们项目的带注释的Verilog代码。
QSys屏幕截图
我们的QSys配置的屏幕快照可以在此处,此处和
C代码
可以在此处找到我们项目的带有注释的PIC32 C代码。
任务分配
尽管该项目的绝大部分工作是共同努力,但Nick大多专注于硬件实现,Julia专注于PIC32测试平台,而Anthony专注于Python GUI。这种故障使我们可以并行工作,并且比以前的工作效率更高。
参考
数据表
原理图
代码参考
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix