Hint: this post is also available in English.

不知道各位还记不记得之前那个WIP的点屏日常,总之出了些岔子没能完全work,而那个屏幕对应的设备是VerilogBoy项目的一部分。虽然应该继续研究,或者做新的板子解决屏幕问题然后继续调VerilogBoy的,但是我现在没有任何焊接工具,所以设计新版本硬件的计划大概是得搁置了。所以这段时间最好是继续完善一下软件和RTL的设计。我大概半年前决定开始重构CPU,结果一直咕咕到现在都还没做完,是不应该着急做硬件,先把这些重构完。然而,我除了没有焊接工具,还没有把我的FPGA开发板带来……不过也不是完全没有办法,我这里至少还有两台Pano Logic瘦客户机可以玩。

新玩具

首先介绍一下Pano Logic瘦客户机,显然原本这就是个瘦客户机,用来连接到远程桌面服务器的。然而与众不同的是,Pano的瘦客户机内部是FPGA,而不是其它瘦客户机上常见的ARM或者x86处理器。Pano把它们称为“零客户端”,也许是表示上面没有运行(零)软件。然而对于他们来说不幸的是,Pano在2013年破产了。而对于我来说幸运的是,这些设备现在对于原本购买他们的公司而言已经变得毫无价值,于是在eBay上就能以非常便宜的价格买到。现在轮到我来折腾这些设备了。当然,在hackaday上已经有了一些关于Pano Logic的报道:

1551068560833-rear.jpg

按照我了解到的,Pano Logic瘦客户机一共有三代产品,前两代看起来非常相似,而第三代则要更薄一些。然而遗憾的是我还从来没有在eBay上见过第三代的机器。如果你了解更多关于第三代的事情……请回复告诉我谢谢。第一代的型号内部是一片Xilinx Spartan-3E XC3S1600E FPGA(1600K系统门数,大约就是30K LUT4),带32MB的板载LPDDR内存。第二代机器,取决于具体的Revision,有可能是Xilinx Spartan-6 XC6SLX150(Rev. B)或者是Xilinx Spartan-6 XC6SLX100(Rev. C),两种Revision都带有128MB的DDR2内存。我有的是Rev. C的机器。两代都已经被玩家给逆向了,主要的开发者有cyrozap, twj42, 和 Tom Verbeure。在以下地址可以找到更多关于Pano瘦机的信息:https://github.com/tomverbeure/panologic,和 https://github.com/tomverbeure/panologic-g2.

以下内容均为机翻

现在,我应该关注哪一代? Gen 2显然更强大,但在eBay上更难找到。 Gen 1,功能强大,足以满足我的目的,仍然可以在eBay上轻松购买。我决定我希望更多的人能够使用我的VerilogBoy代码(如果有的话),所以我将使用Gen 1.此外,我为Gen 1设备开发的所有框架都可以帮助其他人研究使用他们自己的G1。

谈到 G1...

然后,第1代已经被逆向工程,有人甚至在网上公布了它的原理图,将现有代码移植到G1应该是微不足道的,对吧?不。还有几个问题需要解决:

  • G1没有任何用户GPIO。为了连接游戏控制器,人们需要重新调整一些IO(如panoman项目,他使用VGA端口的I2C),或使用USB操纵杆。这意味着我需要在FPGA上的软核上运行主机端USB堆栈。正如我所说,我希望更多的人可以玩这个,所以我会选择USB解决方案。
  • G1没有任何大到足以容纳游戏的机载存储空间。典型GameBoy游戏的容量在32KB到4MB之间。口袋妖怪和塞尔达传说等着名游戏大约1MB。 G1仅具有1MB SPI闪存,并且比特流占用600KB。更糟糕的是,G1没有SD或CF卡插槽,我们需要从以太网或USB加载游戏。好吧,因为我已经需要USB操纵杆,我将使用USB来加载游戏。

现在游戏将被加载到哪里?唯一可容纳1MB游戏的存储元件是LPDDR内存。起初我觉得这很容易,我只需要使用Xilinx的MIG为我生成一个内存控制器,我就像使用它一样使用它,而我之前就已经在ML505上完成了。好吧,不。 Spartan-3E MIG不支持LPDDR,总线宽度限制为16位(pano上的内存为32位)。我找不到Spartan-3E在线的任何开源LPDDR控制器核心。

结论:我必须自己制作一个。

所以这就是这篇文章的全部内容:为Spartan-3E制作LPDDR控制器。

DDR基础知识

我真的不想具体介绍DDR SDRAM的所有细节,网上有大量的信息。在这里,我将通过一些基本的东西来记住:

  • 如果忽略SDRAM所需的初始化和刷新,协议非常简单,就像SRAM一样,主机发送地址,内存发送回数据。
  • 与使用线性加法的普通SRAM不同,SDRAM使用2D寻址。主机需要先发出行地址,然后发送列地址。但您也可以将其视为线性地址,在不同时间传输高位和低位。
  • 由于一切都是相对较高的速度,延迟成为一个需要考虑的事情。相对于时钟周期,互连和逻辑延迟变得显着。
  • 相对于时钟周期,上升时间和下降时间变得非常重要。这就是为什么在DDR控制器中,通常手动延迟或相移信号,因此时钟上升沿不与数据边缘对齐,而是与数据中心对齐。
  • 有一个称为DQS或数据选通的信号,用作数据线的时钟。通常,数据线的每个字节组都具有相应的DQS线。这允许设计者仅控制一个字节内的延迟,而不是跨越不同的字节。

低功耗DDR

首先,LPDDR SDRAM和DDR SDRAM有什么区别? Micron有一个非常好的技术说明:TN4615 - 低功耗与标准DDR SDRAM。我只想重申一下要点:

  1. DDR使用SSTL18-I IO标准,而LPDDR使用LVCMOS18 IO标准。
  2. DDR具有内部DLL(延迟锁定环),意味着存在最小频率限制,数据总线,数据选通和时钟是相位对齐的; LPDDR没有内部DLL,意味着对最低频率没有任何限制,但数据总线,数据选通和时钟不再是相位对齐的。
  3. LPDDR具有PASR和TCSR功能,有助于降低待机电流。
  4. 由于缺少DLL,初始化过程与DDR不同。

就我而言,只有1,2和4与我有关。

1551068886192-waveform_ddr_vs_lpddr.png

(由于缺少DLL而产生的差异)

从哪开始?

编写DDR控制器主要的问题就是解决延迟问题。需要小心控制信号与信号之间的延迟以使其工作,并且输入选通信号需要被延迟恰好使用的1/4周期(90度相移)。在Spartan-3E上,精确延迟信号的唯一方法可能是将LUT链接在一起并构建一个自动校准电路来补偿温度变化。我自己也懒得写这些。因此,我将从一个可用的DDR控制器开始,并使其适用于LPDDR内存。

那么,哪一个?在线有很多DDR控制器,很少有Spartan-3E。我终于决定用MIG作为起点。尽管经常被批评为臃肿,但MIG有一个重要特征:它根据数据选通(DQS)信号锁存数据。

作为背景信息,DDR存储器将同时输出数据和数据选通信号,主机应将数据锁存在数据选通(DQS)信号的两个边沿。通常,人们会想要将DQS移动90度,因此DQS的边缘是有效数据窗口的中间。

还记得我提到了阶段关系问题吗?许多SDR / DDR控制器都很懒惰:它们将根据内部时钟而不是数据选通来锁存输入数据。这是有效的,因为如果忽略延迟,DQS应该等于DRAM CLK,并且CLK来自控制器的内部时钟。

这将带来两个重要的好处:

首先,整个数据路径现在位于一个时钟域中。如果使用DQS锁存数据,则意味着数据路径将进入主时钟和DQS两个时钟域。

其次,不再需要延迟DQS信号以获得90度移位,因为它不再用于锁存数据。虽然仍然需要移位内部时钟,但这与FPGA的内部DLL或PLL无关。这极大地简化了控制器的设计。

缺点是基本上会失去一些时序余量,如果它只是DDR而不是DDR4,这不是什么大问题。然而,在LPDDR中,关键是DQS和时钟之间不再存在相位关系,控制器必须使用DQS来锁存数据。

MIG是我发现使用DQS的唯一一个,因此这是唯一的选择。

修改MIG

我修改MIG的经验是,我应该说“意外”。 适应性出乎意料地容易,但事情在意想不到的地方破裂。

首先从修改初始化过程开始。 不管你相信与否,这只是为了使MIG与LPDDR一起工作所需的修改! (但只需使用MIG即可进行其他修改)

在MIG中,初始化由FSM处理,它位于第980行的mig_controller_0.v内。您可以在此处检查我的代码:

https://github.com/zephray/VerilogBoy/blob/refactor/target/panog1/fpga/mig/mig_controller_0.v

是的,这就是让MIG与LPDDR一起工作的全部内容。 然而,人们很可能也希望稍微调整MIG自身的行为,比如改变地址位的排列方式(我喜欢将它设置为{row,bank,column},而S3E MIG的默认配置是{row,column,bank }),或更改参数(如设置不同的突发长度或CAS延迟)。

RISC-V!

现在,内存控制器有了,我可能应该将它连接到测试它是否真的有效。 我可以使用提供的MIG示例,它有数据模式生成器并将自动测试控制器,但我不太喜欢这种方法:它会告诉我成功或失败。 我想知道更多。

所以我决定将它连接到PicoRV32软处理器,并使用一块内存测试代码来测试内存是否真正有效。 当然,需要一个简单的MIG到PicoRV32桥才能使其工作。 它只不过是一个(可能是效率低下的)FSM。 您可能会在此处看到源代码:

https://github.com/zephray/VerilogBoy/blob/refactor/target/panog1/fpga/mig_picorv_bridge.v

(我将在桥中加入缓存,因此如果您看到缓存并想要检查没有缓存的版本,请查找该文件的历史版本。)

现在,MIG准备好了,CPU在,时间生成比特流并进行测试? 好吧,不。 在尝试使用电路板之前始终进行模拟。 Micron提供了LPDDR RAM verilog模型,我们可以使用它并将其连接到我们的板顶级文件并运行模拟。 验证CPU,桥和MIG是否一起工作,并且模型不报告任何错误。

1551068899842-20190224111128.png

(模拟告诉你甚至逻辑分析器都不能:未定义的值,未定义的值在实际硬件上将是0或1,并且很难说。)

模拟也通过了,生成比特流的时间? 还没。 我仍然需要编写约束文件。

设计约束 - 从行为仿真到实际的FPGA板

首先是LPDDR的所有IO引脚的定义。 正如我之前提到的,LPDDR使用LVCMOS IO标准而不是SSTL-I,这意味着我们不再能够将DIFF-SSTL-I IO标准用于差分时钟引脚,并且差分时钟必须像两个时钟驱动一样驱动 单端信号。 嗯,这不是什么大问题,我们只是摆脱MIG的差分时钟缓冲区,但使用两个隐含的单端OBUF:

assign LPDDR_CK_P = clk_100;
assign LPDDR_CK_N = clk_100_180;

而且仅供参考,我尝试在LPDDR上使用DIFF-SSTL-I作为时钟信号,没有成功,没有数据回读是正确的。

由于DDR控制器对时序敏感,因此需要向MIG添加更多与延迟相关的约束。 幸运的是,Xilinx提供了它们。 首先将示例ucf文件复制到项目中,然后将其添加到项目中。 删除与电路板相关的所有内容,如我们自己的ucf文件中所述。 然后,重命名信号以匹配我们的设计。

尝试合成并实现设计,如果ISE抱怨无法找到某些东西,请使用Post-Synthesis资源管理器进行双重检查。 如果它是对基元的引用,则在路径后添加“*”。 在修复所有错误之后,它应该完成实现,但是有大量失败的时序约束。 我们可以看到它是因为IOB离CLB太远了:

1551068914030-failing_constraint.png 295/5000

没关系,因为我要解决这个问题。 我的想法是,延迟线和FIFO应尽可能靠近引脚的IOB。 可以使用FPGA浏览器找到IOB旁边最近的CLB:找到右边的信号,看看它旁边的CLB的坐标是多么容易。

1551068916782-clb_and_iob.png

这是每个DDR信号旁边的CLB Y坐标,我已经找到它们,所以你不需要再做它。

  • DQ0\1 – Y0\1
  • DQ2\3 – Y4\5
  • DQ4\5 – Y32\33
  • DQ6\7 – Y48\49
  • DQS0 – Y8\9
  • DQ8\9 – Y52\53
  • DQ10\11 – Y56\57
  • DQ12\13 – Y60\61
  • DQ14\15 – Y72\Y73
  • DQS1 – Y64\65
  • DQ16\17 – Y84\85
  • DQ18\19 – Y88\89
  • DQ20\21 – Y96\97
  • DQ22\23 – Y104\105
  • DQS2 – Y80\81
  • DQ24\25 – Y100\101
  • DQ26\27 – Y116\117
  • DQ28\29 – Y144\145
  • DQ30\31 – Y148\149
  • DQS3 – Y140\141

回到UCF。 查看所有地点分配? 那么你可能不需要触摸任何与校准相关的东西,它们独立于数据路径,可以位于FPGA内的任何位置。 Xilinx决定将它们放在FPGA的中间。 我们关心的是FIFO位和DQS延迟线的位置。 现在MIG的规则是:

  • DQS延迟线必须紧邻IOB。
  • FIFO写地址和写使能应紧邻延迟线。
  • 各个FIFO位可以位于DQS或DQ IOB附近,但不一定紧挨着DQS或DQ IOB。

更改相关基元的站点分配并再次运行实现,现在希望时间约束全部通过。

在硬件上进行测试

最后是时候进行测试了! 如果第一次就能用我会很惊讶的...结果是:

1551068922655-DSC_4740.jpg

有趣。如果您说数据大部分是正确的,那么基本上正常工作,或者如果您说它基本上是在1秒钟内失败,则基本上没法正常工作。

好吧,我花了一天时间来调试整个东西,在这里我只是跳到结论:

我至少知道有4种方法可能可以解决这个问题:

  • 问题可能是由于错误的延迟线分接头设置引起的,这意味着数据可能过早或过晚被锁存。虽然赛灵思有一个校准电路可以确定最佳分接值,但它对我不起作用。报告的价值高于实际应有的价值。
  • 如果您看到一致的位错误,请尝试调整FIFO位的站点分配,它可能会有所帮助,也可能没有帮助。
  • 如果您看到一致的位错误并且之前的方法没有帮助,请尝试调整该特定位的延迟,您可以调整IBUF内该特定位的延迟值。默认情况下,它们都设置为零。 (如果要减少该位的延迟,请增加所有其他位的延迟,并增加DQS延迟)。
  • 将所有LPDDR IO的SLEW设置为FAST。

结果,我使用了1,2和4的组合。不要忘记使用更复杂的测试模式来测试DRAM。我还编写了一个程序来循环显示不同的延迟抽头值,看看哪个工作效果最好:

1551068924089-DSC_4742.jpg

你可以看到0x00-0x17正常工作。 这是将转换速率设置为FAST。 如果是SLOW,则只能使用延迟值0x01-0x04。

32位MIG

现在16位模式正在工作,那么32位呢?

虽然Spartan3E MIG仅支持16位模式,但MIG本身能够处理Spartan-3和3A上的64位存储器。 毕竟它是同一个MIG。 只需编辑mig_parameters_0.v即可启用32位支持:

`define   DATA_WIDTH                               32
`define   DATA_STROBE_WIDTH                        4
`define   DATA_MASK_WIDTH                          4

添加数据和DQS线的约束,修改PicoRV-MIG桥以支持32位模式,并准备好进行测试...然后ISE将告诉您真正的问题:

1552140897707-photo_2019-02-24_15-34-14.jpg

怎么回事?

两个IOB(一对IOB)共享两个相同的时钟线:

1552140903554-20190302084700.png

在这样一对中,应该使用不超过2个时钟通道来输出数据。

通常,如果它是两个SDR信号,则一个IOB可以使用一个时钟通道,并且根本不会发生任何冲突。但如果它是DDR信号,则IOB需要两个时钟(原始时钟和180度移位时钟)才能工作。因此,在一对中,IOB将使用相同的时钟(如上面的屏幕截图所示),或者只能使用一个信号。

在Pano Logic上,DM0和DM1属于同一个IOB对,因此它们可以很好地协同工作。但无论出于何种原因,这不是DM2和DM3的情况,它们与其他信号共享时钟,一个是DQS,另一个是RAS。 DM和DQ信号使用90度和270度移位时钟进行输出,而RAS使用0度时钟。如果是这样的一对,则需要3个时钟,它根本不起作用。

解决方法:忘记DM信号,将它们绑在地上。 DM(数据掩码)信号用于屏蔽未使用的数据字节,因此它们将被RAM忽略。当你想写少于最小传输长度的字节时使用它(对于32位LPDDR存储器,它是8个字节,但MIG只能支持16个字节。)所以如果我们总是写入所有字节转移时,可以忽略数据掩码。

但是PicoRV32可以使用32位(4字节)传输,如何将其“扩展”为16个字节?答案是缓存。我想这不属于这个日志的范围,所以我将继续讨论下一个日志中的缓存主题。

在此之前,我们仍然可以使用PicoRV32来测试32位版本的控制器,只需每16字节写入并验证4个字节作为解决方法。这将测试32位接口,但将可用容量限制为8MB。它实际上只是用于测试。

结果当然是有效的。

结论

  • LPDDR控制器在16位和32位模式下工作,@ 100MHz。
  • 看看目前的时间报告,我认为100MHz确实已经是上限了。
  • 如果要在32位模式下使用LPDDR,则几乎必须使用缓存。 要避免使用缓存,请坚持使用16位模式。
  • 源代码:https://github.com/zephray/VerilogBoy/tree/f546e0789a65e48243175fab955df40a9778f3e6,在target / panog1文件夹下。 这适用于16位模式。 随意复制您需要的任何东西。