惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
D
Docker
S
SegmentFault 最新的问题
博客园 - 聂微东
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
GbyAI
GbyAI
L
LangChain Blog
Vercel News
Vercel News
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
T
Threatpost
Scott Helme
Scott Helme
T
Tailwind CSS Blog
Latest news
Latest news
Stack Overflow Blog
Stack Overflow Blog
Blog — PlanetScale
Blog — PlanetScale
The Register - Security
The Register - Security
罗磊的独立博客
P
Proofpoint News Feed
腾讯CDC
S
Schneier on Security
雷峰网
雷峰网
A
About on SuperTechFans
T
Tenable Blog
F
Full Disclosure
Cyberwarzone
Cyberwarzone
博客园_首页
有赞技术团队
有赞技术团队
K
Kaspersky official blog

文章列表

游戏玩后感:ReLief:献给亲爱的你 我的周边(谷子)分享 游戏玩后感:Kanon 简谱:致真实的你 《Rust中常见的有关生命周期的误解》学习笔记 简谱:StarMap 简谱:かく咲きたらばいと恋ひめやも 简谱:东风 简谱:无法诉说的思念 简谱:Girlish 游戏玩后感:时钟机关的Layline 简谱:风之琶音 简谱:星空的记忆 简谱:因为遇见了你 简谱:月童 番茄简谱脚本转调器 游戏玩后感:青空下的约定:Refine 游戏玩后感:在这苍穹展翅 书籍读后感:控制论与科学方法论 游戏玩后感:恋爱表达式 游戏玩后感:樱之诗 MLIR-tutorial学习笔记 游戏玩后感:潜伏之赤途 游戏玩后感:纯爱咖啡厅:帕露菲重制版 游戏玩后感:智以泪聚 游戏玩后感:初雪樱 游戏玩后感:告别回忆:从今以后 游戏玩后感:梦灯花 游戏玩后感:金辉恋曲四重奏 游戏玩后感:五彩斑斓的世界 昇腾310P使用记录 游戏玩后感:AIR 游戏玩后感:弹丸论破 游戏玩后感:流景之海的艾佩莉亚 Xilinx_HLS上板过程记录 游戏玩后感:告别回忆2 游戏玩后感:恋爱绮谭 Faiss和Rapidsai_Raft使用记录 游戏玩后感:近月少女的礼仪 游戏玩后感:樱色之云,绯色之恋 游戏玩后感:幸运草的约定 游戏玩后感:星之梦、候鸟和丸子与银河龙 游戏玩后感:白色相簿2 Windows上使用VTune分析PyTorchExtension调用的Cpp程序 游戏玩后感:仰望夜空的星辰 最简单的算卦方法之一:梅花易数法 游戏玩后感:苍之彼方的四重奏 krkr引擎解包工具介绍 游戏玩后感:星空的记忆 游戏玩后感:LittleBustersEX 游戏玩后感:SummerPockets 游戏玩后感:逆转裁判 动漫观后感:吹响吧上低音号 git基本操作
SpinalHDL上板过程记录
VnYzm · 2023-11-26 · via

背景

最近帮老师做一个硬件项目,使用SpinalHDL实现。实际用起来还是觉得这玩意不错,它能够抽象到“生成Verilog代码”这一层面,通过程序简化生成的逻辑,可以减少很多直接用Verilog需要编写的重复代码。同时它声明的端口名称和硬件逻辑是能够直接对应到Verilog代码的,所以查看波形调试也比较方便,这就比Chisel乃至HLS有优势了。不过Scala我用起来还是不太习惯,主要是它同时包含了函数式和面向对象的大量特性,很多概念我也不是很熟悉,只能勉强着用。

当然,SpinalHDL有个缺点,那就是资料和教程太少,中文和英文的都一样,所以刚开始做项目的时候老师也在担心SpinalHDL开发会不会遇到什么问题。好在最后在FPGA开发板上顺利跑通了。硬件代码本身的开发没什么好说的,主要是仿真跑通后到在真正硬件上跑通颇费了一番周折。所以这里记录一下SpinalHDL上板过程遇到的几个问题,也算是抛砖引玉,希望大佬们补充一下SpinalHDL资料和教程在这方面的空白吧。

片上存储

先说一下SpinalHDL的片上存储模块,也就是最基本的Mem。假设我需要一个宽度为64bits,深度为65536的片上存储,SpinalHDL里是这样写的:

val ram = Mem(Bits(64 bits), 1 << 16)

在Verilog里,它会生成一个寄存器数组:

reg [63:0] ram[0:65535];

正常情况下,Vivado能够自动将这个寄存器数组例化为BRAM,当然,这是有前提的,就是这个寄存器数组的读写逻辑符合BRAM的端口配置,即最多为两个读写端口,否则综合的时候就会报错。那么SpinalHDL里如何指定端口数目呢?其实就是看程序里执行了多少条writereadAsyncreadSyncreadWriteSync方法,一条对应一个端口。前面三个方法对应的是只读/只写端口,最后一个对应读写端口。

注意只读和只写逻辑不会自动合并为读写端口,假设一个模块调用了writereadSyncreadWriteSync三个方法,那么将会生成一个只写端口,一个只读端口和一个读写端口,将无法通过Vivado的综合流程。因此,如果模块中同时包含和读数据相关的信号和写数据相关的信号,又希望这两条信号共用一个读写端口,则只能用readWriteSync方法实现,然后通过多路选择器和读、写使能信号控制读和写的选择。

AxiLite

我们做的项目用的是Zynq系列的开发板。前面的文章也说过,这种开发板上运行的硬件程序主要通过AxiLite总线接收控制信息。AxiLite在SpinalHDL里有支持:

val io = new Bundle {
  val axilite = slave(AxiLite4(addressWidth=6, dataWidth=32))
}
val registers = new AxiLite4SlaveFactory(io.instruction)

即可声明一个控制最多64个32位寄存器的AxiLite总线,之后就可以通过BasSlaveFactory提供的相关方法绑定和控制这些寄存器了,似乎也可以通过RegInterface实现类似的工作,不过我没有尝试。

仿真的话就使用AxiLite4Driver即可:

val axilite = AxiLite4Driver(dut.io.axilite, dut.clockDomain)
axilite.write(0x4, BigInt("2333", 16))
println(axilite.read(0x4).toInt)

AxiLite4Driver文档里好像没有介绍用法,因此需要自己查阅源码

直接把SpinalHDL生成的Verilog放进Vivado里,生成的IP核AxiLite相关的端口都是分散的,在Block Design的时候不会被正确连接,因此还需要使用Vivado的打包功能Create and Package New IP,然后在Ports and Interfaces里创建Axi映射,将Verilog里Axilite对应端口和Vivado里的Axilite信号映射起来,之后在Addressing and Memory里查看刚映射的端口地址有没有被自动配置上,注意自动配置经常会没有更新,这时得多重启几次项目,确保自动配置完成才行。打包完的IP核就可以在Block Design里顺利以Axilite的方式连接了。

另一个需要密切注意的地方就是SpinalHDL的清零端口是高电平清零(reset),这个和Vivado里其他IP核常见的低电平清零(resetn)不一样。因此在使用Block Design的清零信号源的时候,注意是将名字带有reset的信号连到我们IP核的清零端,而不是名字带有resetn的。这一点坑了我非常久,因为清零信号始终为高电平导致IP一直清零,无法正常工作,也不能接收任何外界信号,让我非常的迷惑。

Axi

大量数据的传输,也就是和DRAM的交互需要使用Axi总线。SpinalHDL通过Axi4接口类支持Axi协议,其中包含Axi4arAxi4rAxi4w等多个通道,分别对应Axi的读地址传输、读数据传输、写数据传输等。由于Axi的Burst传输逻辑比较复杂,所以可以用一些别人写好的类,如DmaUnit等(其实我觉得SpinalHDL官方应该提供一些开箱即用的模块,像这个DmaUnit里也存在一些逻辑上的问题,用的时候总觉得胆战心惊的)。另外,注意Axi端口是master,和Axilite端口是slave不一样。

仿真则有官方模块,即AxiMemorySim

val dramSim = AxiMemorySim(dut.io.axi, dut.clockDomain, new AxiMemorySimConfig)
dramSim.memory.loadBinary(0x0, "dram32.bin")
dramSim.start()
// ...
dramSim.memory.saveBinary(0x0, 4096, "output.bin")

Axilite4Driver一样,没有官方文档,需要查阅源码才知道用法。

上板的时候和Axilite一样,需要在Vivado里打包IP功能里创建新的Axi映射,映射对应端口和信号,才能在Block Design里顺利连接。

关于Axi,需要注意的一点是SpinalHDL默认的是Axi4协议,而部分FPGA的PS核只支持最高Axi3协议,这个可以双击PS核查看。这两个协议差别不是很大,只有两点比较重要:一个是两者支持的最高Burst Size不一样,Axi3的比较小,最高为16,所以如果要在这样的FPGA上运行需要将硬件模块的Burst Size也设置成16;另一个是两者的Burst Cache含义不同,因此如果你不是非常熟悉Burst Cache的话,保险起见,直接将Axi读地址和写地址通道的cache端口置0或者直接在Axi的构造参数配置类Axi4Config里将useCache设置为0。我就被后者坑了很久,cache端口设置错了,结果读数据和写数据返回的resp全部为3(0为正常),我看了网上的资料还以为是no slave,查了半天连线和地址映射,一直没想到是缓存的问题。硬件博大精深,像我这种小白出了错只能盲试然后把希望交给运气😥,所以才希望将经验留给后来者,贯彻人人为我为我人人的雷锋精神😎。

总结

这次感觉SpinalHDL确实是蛮好用的,而且也证明了使用SpinalHDL设计硬件原型并在FPGA上跑通的可行性,至少在体系结构的科研方面确实是一个好用的工具。希望我的这篇文章能帮大家解决一些类似的问题,大家也来使用SpinalHDL吧!