跳转至

hardware

用 PulseView 配合 DSLogic 调试 SPI Flash

最近需要用到逻辑分析仪来调试 SPI Flash,设备是 DreamSourceLab 的 DSLogic,最开始用的是官方的 DSView,确实能够抓到 SPI 的信号,也可以解析出一些 SPI Flash 的数据,但是很多是不完整的。

后来把源码下载下来,发现是基于 sigrok 和 PulseView 做的一个魔改版,然后 sigrok 官网上最新的版本已经支持了 DSLogic,于是就用 PulseView 替代 DSView。一开始遇到的问题是没有 firmware,一番搜索找到了解决方案,按照脚本下载好文件即可。

进到 PulseView 以后,把 SPI 的四路信号接上,然后抓了一段信号,解析:

可以看到它正确地解析出来了 Fast Read 命令。由于 DSView 它 fork 自一个比较老的版本,所以它并不能正确解析出来。

P.S. Linux 下它界面显示比 macOS 下好看一些,估计是没有适配好。

在 FPGA 上实现路由器(3)

前言

又半个月过去了,在写了上篇系列博文之后也是做了很多新的更改。上次做的主要是关于性能方面的提升,怎么提高频率,从而达到比较大的流量,而这段时间做的则是功能,做实现 RIP 协议和转发表的动态更新。

软件部分

软件部分目前是用 C 代码写的,用 Xilinx SDK 提供的各个 AXI 外设的驱动和 PS 自己的驱动,实现了所需要的,RIP 协议的处理,转发表的更新和统计信息的读取。

实际上做的时候比较粗暴,主要是通过三种 AXI 外设与硬件部分进行交互:AXI Stream FIFO,AXI GPIO 和 AXI BRAM Controller。其中 AXI Stream FIFO 是用来接收和发送需要 CPU 处理的以太网帧的,AXI GPIO 则是用来读取统计的信息,AXI BRAM Controller 是用来读写转发表的。最后在顶层设计中把这些外设连接起来。

硬件部分

硬件部分还是继续之前的部分往下写,添加了统计信息,直接暴露出去,让 CPU 走 AXI GPIO 读,因为不需要很高的精确度;转发表本身,一开始想的是自己写一些接口转换,后来发现,直接用 True Dual Port RAM 然后把一个 port 暴露给 AXI BRAM Controller 即可,免去了各种麻烦,PS 可以直接进行修改,不需要额外的工作。

最终效果

为了测试这套东西是否正常工作,就开了两个 Arch Linux 的虚拟机,分别 Bridge 到两个千兆的 USB 网卡上,都连到 FPGA 上。然后在两边都配上了 BIRD,配置 RIP 和一些路由,确实能更新硬件的转发表,并两边的 RIP 可以学习到对方的路由。

在 FPGA 上实现路由器(2)

前言

月初的时候,有了一个完整可用的路由器(上一篇系列博文),但当时测了一下速度,只有几十 Mb/s,只要往上提就会失效,得 reset 才能继续。当时也先没管性能的事情,先把和 OS 交互的部分做了。现在又回头来做性能调优。

之前,逻辑部分的主频只有 10 MHz,这自然不行,不提高肯定做不到千兆。于是试着把主频拉高,FIFO 加大,然后遇到了很多问题,慢慢修复了,学到了很多新知识,目前也接近千兆的水平了吧,贴图:

TCP 测速:

UDP 测速:

测试环境是 macOS 虚拟机外打虚拟机内,走网桥把虚拟机和一个 USB 网卡接起来,然后从另一个 USB 网卡打到路由器。

尝试 700Mb/s

接下来讲讲,在这个过程中遇到了什么问题,怎么解决的。第一个是速度过快就会挂,这肯定是丢包逻辑没写对,后来在仿真里开够了时间,于是就找到了一个 BUG,其实就是一行的修复。接着就是提高主频,但大家也知道,CPU 不能随便超频,由于各种延迟的原因,比如 Setup 时间,如果超了一个时钟周期的时间,本来应该下个周期就得到新数据的,结果到了下下周期才有,那有的状态可能就乱了,我目前遇到的也主要就是这个问题。

于是就对着 Timing 里汇报的各种问题修啊修,发现了很多以前没有注意到的问题,它们不影响功能,但是会让逻辑变慢。第一个问题是 High Fanout,以上就是说一个输出接到了很多输入,这看起来没啥问题,但数设课上也讲过,每个门的输入输出电流是有限制的,例如按书上的数据,一个门输出只能带十个门,更多只能级联一层。级联的话,延迟自然就高了。后来发现,这里的原因是,开了一个大的数组,但是没有变成 RAM,综合出了几千个逻辑单元,自然是出问题。解决方法很简单,用 xpm_memory_tpdram 即可。这样一搞,主频就能上 200MHz 了。

这个时候测了一下,发现 UDP 能打到 700Mb/s 了,TCP 由于丢包率比较高,只有 400Mb/s,距离预期还有一段距离。于是继续进行优化。

向 900Mb/s 进发

要继续提速,自然要提高主频。下一个主频目标就是 250MHz。随着提高主频,时序的要求也会更高,自然也出现了新的问题。

这次的问题主要在于,一个路径上逻辑门数过多,多的有 7 到 10 个,每一步零点几到一点几纳秒,叠起来 4 纳秒哪里够用。于是把一些不需要依赖条件的逻辑挪到条件外面,这样就减少了一些路径的依赖。

解决了这个以后,现在的 WNS(Worst Negative Slack)只剩下 0.6 ns 了。这时候的问题一部分还是来自于逻辑门过多,但这个时候就没这么简单了,只能继续细化流水线,打一拍,这样才能把延迟降下来。

虽然 Timing 没有完全解决,但还是写进了 FPGA 中。幸好工作一切正常,就得到了上面那个图片的结果,接近千兆的速度了。

后续优化途径

后续优化途径的话,一方面是继续对逻辑进行细化和拆分,另一方面是对架构进行优化。当初设计的时候,可能没有考虑地那么周全,例如现在发现,其实 ARP Cache 可以放到每个网口一份,这样会降低花在仲裁上的时间。另外,路由表其实也可以分多份存,只要改的时候同一改就可以了。这样应该会更快。

在 FPGA 上实现路由器

最近在做 FPGA 上硬件的路由器,感觉接近一个基本可用的阶段了吧,大概谈一谈做这个的思路、过程和踩过的坑。

首先,做实验用的板子是 Alinx AX7021,FPGA 是 Xilinx xc7z020clg484-2,扩展板上有 4PL+1PS 个网口和千兆 KSZ9031RNX PHY,采用的接口是 RGMII。一开始做的自然是做 RGMII,但是遇到了困难,RGMII 在千兆模式下传输的是 DDR 信号,而时序和延迟就是个比较麻烦的事情。一开始先直接拿 Xilinx 的 AXI Ethernet IP 来用,然后上 ILA 看到了 IDDR 后的信号,第一次看到了完整的以太网帧,从 Preamble 和 SFD 到最后的 FCS。于是就特别振奋,想着手写 RGMII,先做收,再做发。确实,收很容易,很快就做出来了,但是写总是出问题,当时也不懂跨时钟域的一些问题,总之各种没调出来。于是就退而求其次,选择了 Xilinx 的 Tri Mode Ethernet IP 了。

Tri Mode Ethernet IP 有很多选项,为了简单,直接采用了 AXI-Stream 的接口,不要 AXI4-Lite 什么的,都不要,因为我需要直接写剩余的逻辑。其他东西能省也都省掉了。这个 IP 确实很给力,很快就可以完成收和发的操作了,这次终于知道了怎么处理跨时钟域的问题 — XPM FIFO ASYNC,一下推进了很大的进度。

既然可以收,也可以发了,就扩展到多个网口。这个 IP 中可以选择 Shared Logic 在内部,也可以在外部,研究了一下发现,应该是一个放内部,其余选外部,然后接起来就可以了。不过目前为了简单,还是只用了俩端口。在这个基础上,就开始解析收进来的以太网帧了。

第一步自然是填 ARP 表,自然问题来了,如果多个网口同时进来数据,怎么保证 ARP 表读写的正确性?自然就想到总线上需要做仲裁,于是写了一个简单的总线仲裁,顺带学习到了 unique case(z)priority case(z) 的语法。然后 ARP 表怎么实现呢,大概就是一个哈希表,然后表里维护了(IP,MAC,PORT)三元组,然后实现了一些冲突和覆盖的处理逻辑,做这些的同时也对各个模块编写相应的测试。有了 ARP 表,就可以在解析以太网帧的时候,拆解出里面的信息,然后请求 ARP 表总线,然后写入。

第二步则是相应 ARP 请求,这就需要发出以太网帧。由于 4 个端口都可能向 4 个端口发出以太网帧,这就需要一个 4x4 matrix + 仲裁。不过目前为了简单,就还没有上 FIFO,直接仲裁进到目的端口的 TX FIFO 中了。这一步并不难,不过在最后 AXI-Stream 的一步遇到了一些困难。由于 Tri Mode Ethernet IP 对 tready 和 tvalid 有特定的要求,所以这里只能用 FWFT FIFO 进行,然后进行了一波神奇的操作,最后搞定了这个事情。成果就是可以从电脑上 arping 通指定的地址了。

第三步,也是正在做的一步,就是真正实现 IP 包的转发,这需要三个步骤:解析目的地址,查询路由表,查询 ARP 表。于是需要照着 ARP 表的方案同样做了路由表的仲裁,目前为了简单也还是把路由表设置为静态的。这里就需要做一些特殊的考虑,例如上面三步是串行的,但是我需要同时把 IP 包存一份,最后转发的时候修改一点就发出去了,所以需要等两步都做完,才能继续下一个包的处理。目前做到了第二小步,正在向最后一步查询 ARP 表进发。

UPDATE:现在最后一步也做好了,但是遇到了小问题,还是不能偷懒,需要写一个 XPM_MEMORY_SPRAM,直接写一个大的数组太浪费 LUT 了。

UPDATE-2019-04-27:It WORKS now. 不过也发现了之前写的 ARP 表有点问题。

向咸鱼派写入 ArchlinuxARM

之前由于我的 macOS 上不知道为啥不能把我的 TF 卡设备放到我的虚拟机里,所以之前就没能刷 ArchLinuxARM 上去。今天我想到了一个方法,完成了这件时期:

$ wget https://mirrors.tuna.tsinghua.edu.cn/archlinuxarm/os/ArchLinuxARM-armv7-latest.tar.gz
$ dd if=/dev/zero of=archlinuxarm.img bs=1M count=1024
$ mkfs.ext4 archlinuxarm.img
$ sudo mkdir -p /mnt/archlinuxarm
$ sudo mount -o loop archlinuxarm.img /mnt/archlinuxarm
$ sudo bsdtar -xpf ArchLinuxARM-armv7-latest.tar.gz -C /mnt/archlinuxarm
$ sudo umount /mnt/archlinuxarm

这样就获得了一个 ext4 的 ArchlinuxARM 镜像。刚好解压出来不到 1G,所以开了 1G 的镜像刚好放得下。然后把 archlinuxarm.img 拷回 macOS,然后用 dd 写进去:

$ sudo dd if=archlinuxarm.img of=/dev/rdisk4s2 bs=1048576

这时候可以确认,我们确实是得到了一个正确的 ext4fs:

$ sudo /usr/local/opt/e2fsprogs/sbin/tune2fs -l /dev/disk4s2

不过,我们实际的分区大小可能不止 1G,所以可以修改一下大小:

$ sudo /usr/local/opt/e2fsprogs/sbin/resize2fs -p /dev/disk4s2

这样就成功地把 ArchlinuxARM 写进去了。默认的用户名和密码都是 root,可以成功通过串口登录。

咸鱼派的启动配置

最近刚拿到了一个咸鱼派的测试板子,准备自己把 U-Boot 和 Linux 内核这一套东西跑通,都用主线的东西,尽量减少魔改的部分。首先是编译 u-boot,我用的是现在的 master 分支的最新版 99431c1c:

$ # Archlinux
$ sudo pacman -Sy arm-none-eabi-gcc
$ make LicheePi_Zero_defconfig
$ make ARCH=arm CROSS_COMPILE=arm-none-eabi- -j24

这时候会得到一个 u-boot-sunxi-with-spl.bin 的文件。我们只要把它写到 SD 卡的 8192 偏移处,就可以把 U-Boot 跑起来了:

$ diskutil unmountDisk /dev/disk4
$ sudo dd if=u-boot-sunxi-with-spl.bin of=/dev/disk4 bs=1024 seek=8

接着我们做一下分区。我采用的是 MBR 分区,这样保证不会和 U-Boot 冲突。使用 fdisk 进行分区,我从 1M 处开始分了一个 10M 的 FAT-32 分区作为启动分区,然后之后都是 EXT4 的系统盘分区。接着就是编译内核。

我用的是八月份时候的 4.18.2 内核,虽然不是很新但也足够新了。一番调整内核参数后,得到了一个可用的内核,然后把 zImage 和 sun8i-v3s-licheepi-zero.dtb 都复制到刚才创建的 FAT-32 启动分区,然后进入 U-Boot 进行启动:

$ setenv bootcmd 'fatload mmc 0 0x41000000 zImage; fatload mmc 0 0x41800000 sun8i-v3s-licheepi-zero.dtb; setenv bootargs console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootwait; bootz 0x41000000 - 0x41800000'
$ saveenv # optional
$ boot

这里一开始遇到了很多坑,比如一直看不到 console,这个是找了 @gaoyichuan 拿到的一份 Kernel Config 进行修改修好的。另一个是进去以后找不到 root,我先是搞了一个有 busybox 的 initrd,进去看发现是能找到 mmc 的,但是有延迟,那么添加上 rootwait 就好了。进去以后就差 rootfs。由于我缺少一个写 ext4 的工具,又发现手上有一个 Raspbian 的镜像,它里面也正好是两个分区,而且架构也同样是 armv7l,我就直接把它烧到 SD 卡中,把 U-Boot 写进去,然后往 boot 分区里写内核和 dtb,然后就成功进去,并且跑起来了。最喜感的就是,进去以后是个 pi@raspberrypi,实际上确是另一个东西。不过,只有当我 apt update 发现用了半小时的时候,我才想起来这其实是是一个嵌入式系统。。

进去以后发现,没有识别到网卡驱动。网上找了 LicheePi Zero 的一个解决方案,但是并不能用,还出现了神奇的 Kernel Oops,怀疑是内核版本太新的问题。我又找到 @icenowy 的一个 Patch ,它终于是解决了这个问题,成功地找到了网卡,并且愉快地 ssh pi@raspberrypi.local 。之后会在咸鱼派那边公布一下我们做的修改。

现在的想法是,把 HomeBridge 搭建到它上面,不过目前来看硬件资源有点紧张,放着会有点慢。可能还是用树莓派做这个事情比较合适。

在荔枝糖(Lichee Tang)上初次体验 FPGA

今天从张宇翔学长那拿到了 荔枝糖(Lichee Tang) 的 FPGA 板子,于是立即开始把前段时间学到的 Verilog 应用上来。不过想到现在我手上没有多少外设,然后又必须远程到 Windows 电脑上去操作,于是先实现了一下 UART 通信。

在网上找到了 ben-marshall/uart 一个简易的实现,很快做到了一直在串口上打印 A 字符。接着我开始尝试实现一个简单的串口回显。一开始,我直接把 UART 读到的数据直接输出,果然可以了,但是一旦传输速率跟不上了,就会丢失数据。于是我添加了 FIFO IP 核,然后把读入的数据存入 FIFO,又从 FIFO 中读取数据写入到 UART 中去。不过发现了一个小 BUG:每次打印的是倒数第二次输入的字符,即丢失了第一个字符。在张宇翔学长的帮助下找到了问题:当 FIFO 的读使能信号为高时,其数据在下一个时钟周期才来,于是解决方案就是等到数据来的时候再向 UART 中写数据:

always @ (posedge clk_in) begin
    uart_tx_en <= uart_fifo_re;
end

这样就解决了这个问题。完整代码在 jiegec/learn_licheetang 中。

在 macOS 上读取移动硬盘的 S.M.A.R.T. 信息

之前想看看自己各个盘的情况,但是发现只能看电脑内置的 SSD 的 S.M.A.R.T 信息,而移动硬盘的都显示:

$ smartctl -a /dev/disk2
smartctl 6.6 2017-11-05 r4594 [Darwin 17.7.0 x86_64] (local build)
Copyright (C) 2002-17, Bruce Allen, Christian Franke, www.smartmontools.org

/dev/disk2: Unable to detect device type
Please specify device type with the -d option.

Use smartctl -h to get a usage summary

一开始我怀疑是个别盘不支持,但换了几个盘都不能工作,问题应该出现在了 USB 上。查了下资料,果然如此。根据 USB devices and smartmontools ,获取 S.M.A.R.T 信息需要直接发送 ATA 命令,但是由于经过了 USB,于是需要进行一个转换,导致无法直接发送 ATA 命令。这个问题自然是有解决方案,大概就是直接把 ATA 命令发送过去(pass-through)。上面这个地址里写到,如果需要在 macOS 上使用,需要安装一个内核驱动。可以找到,源码在 kasbert/OS-X-SAT-SMART-Driver 并且有一个带签名的安装包在 External USB / FireWire drive diagnostics support 中可以下载。丢到 VirusTotal 上没查出问题,用 v0.8 版本安装好后就成功地读取到了移动硬盘的 S.M.A.R.T 信息了。

然后我又简单研究了一下各个 S.M.A.R.T 各个值的含义是什么。 VALUE 代表当前的值, WORST 代表目前检测到的最差的值, THRESH 代表损坏阈值。这些值都是从 RAW_VALUE 进行计算后归一化而来。然后 TYPE 分为两种,一是 Pre-fail ,代表如果这一项的值小于阈值,代表这个机器很危险了,赶紧拷数据丢掉吧。二是 Old_age ,代表如果这一项小于阈值,代表这个机器比较老了,但还没坏。真正要看是否坏了,可以看 When_Failed 一栏。