SpinalHDL之通信接口

本文作为SpinalHDL学习笔记第二十四篇,介绍SpinalHDL通信接口相关API。

SpinalHDL学习笔记总纲链接如下:

SpinalHDL 学习笔记_spinalhdl blackbox-CSDN博客

目录:

1.SPI XDR

2.串口

3.USB设备

4.USB OHCI

1.SPI XDR

这是一个 SPI 控制器,它支持:

• 半双工/全双工

• 单线/双线/四线 SPI

• SDR/DDR/.. 数据速率

可以在此处找到其 APB3 实现:

https://github.com/SpinalHDL/SpinalHDL/blob/68b6158700fc2440ea7980406f927262c004faca/lib/src/main/scala/spinal/lib/com/spi/xdr/Apb3SpiXdrMasterCtrl.scala#L43

配置

这是一个示例。

Apb3SpiXdrMasterCtrl(
SpiXdrMasterCtrl.MemoryMappingParameters(
SpiXdrMasterCtrl.Parameters(
dataWidth = 8, // Each transfer will be 8 bits
timerWidth = 12, // The timer is used to slow down the transmition
spi = SpiXdrParameter( //Specify the physical SPI interface
dataWidth = 4, //Number of physical SPI data pins
ioRate = 1, //Specify the number of transfer that each spi pin can do per␣
,→clock 1 => SDR, 2 => DDR
ssWidth = 1 //Number of chip selects
)
)
.addFullDuplex(id = 0) // Add support for regular SPI (MISO / MOSI) using the mode id 0
.addHalfDuplex( // Add another mode
id = 1, // mapped on mode id 1
rate = 1, // When rate is 1, the clock will do up to one toggle per cycle,divided by the (timer+1)
// When rate bigger (ex 2), the controller will ignore the timer,and use the SpiXdrParameter.ioRate
// capabilities to emit up to "rate" transition per clock cycle.
ddr = false, // sdr => 1 bit per SPI clock, DDR => 2 bits per SPI clock
spiWidth = 4 //Number of physical SPI data pin used for serialisation
),
cmdFifoDepth = 32,
rspFifoDepth = 32,
xip = null
)
)

软件驱动

看:

https://github.com/SpinalHDL/SaxonSoc/blob/dev-0.3/software/standalone/driver/spi.h

https://github.com/SpinalHDL/SaxonSoc/blob/dev-0.3/software/standalone/spiDemo/src/main.c

2.串口

例如, UART 协议可用于发送和接收 RS232/RS485 帧。

有一个 8 位帧的示例,无奇偶校验和一位停止位:

总线定义

case class Uart() extends Bundle with IMasterSlave {
val txd = Bool() // Used to emit frames
val rxd = Bool() // Used to receive frames
override def asMaster(): Unit = {
out(txd)
in(rxd)
}
}

UartCtrl

库中实现了 Uart 控制器。该控制器的特性是使用一个采样窗口读取 rxd 引脚,然后使用多数投票制来过滤其值。

IO 名称方向类型描述
configinUartCtrlConfg用于设置控制器的时钟分频器/奇偶校验/停止/数据长度
writeslaveStream[Bits]用于请求帧传输的反压流端口
readmasterFlow[Bits]用于接收解码帧的流端口
uartmasterUart与实际实现的连接接口

控制器可以通过一个 UartCtrlGenerics 配置对象来实例化:

属性类型描述
dataWidthMaxInt帧内最大位数
clockDividerWidthInt内部时钟分频器的位宽
preSamplingSizeInt指定在一个 UART 波特开始时丢弃多少 samplingTick
samplingSizeInt指定有多少 samplingTick 用于采样 UART 波特中段的 rxd 值
postSamplingSizeInt指定在 UART 波特结束时丢弃多少个 samplingTick

3.USB设备

SpinalHDL 库中存在一个 USB 设备控制器。

用几个要点总结支持的功能:

• 实现了允许 CPU 配置和管理端点

• 存储端点状态和事务描述符的内部 RAM

• 多达 6 个端点(几乎没有额外开销)

• 支持全速 USB 主机(12Mbps)

• 在 Linux 上使用自己的驱动程序进行测试 (https://github.com/SpinalHDL/linux/blob/dev/drivers/usb/

gadget/udc/spinal_udc.c)

• 用于配置的 Bmb 内存接口

• 内部物理层需要一个时钟,该时钟需为 12 Mhz 的倍数,至少 48 Mhz

• 控制器频率不受限制

• 无需外部物理层

Linux 小工具经过测试且功能正常:

• 串行连接

• 以太网连接

• 大容量存储(ArtyA7 Linux 上约为 8 Mbps)

部署:

https://github.com/SpinalHDL/SaxonSoc/tree/dev-0.3/bsp/digilent/ArtyA7SmpLinux

https://github.com/SpinalHDL/SaxonSoc/tree/dev-0.3/bsp/radiona/ulx3s/smp

架构

控制器由以下部分组成:

• 一小部分控制寄存器

• 一个用于存储端点状态、传输描述符和端点 0 配置数据的内部 RAM。

每个端点的描述符链表时用于处理 USB 出入 (IN/OUT) 事务和数据。

端点 0 也像所有其他端点一样管理出入 USB 的传输事务,但也会有一些额外的硬件来管理设置 (SETUP)

事务:

• 它的链表在每次设置事务时都会被清除

• 来自设置 (SETUP) 事务的数据存储在固定位置 (SETUP_DATA)

• 它有一个用于设置 (SETUP) 事务的特定中断标志

寄存器

请注意,控制器的所有寄存器和存储器只能以 32 位字的访问方式进行访问,不支持字节访问。

帧 FRAME (0xFF00)

名称类型描述
usbFrameIdRO31-0当前 USB 帧的 ID

地址 ADDRESS (0xFF04)

名称类型描述
addressWO6-0设备将仅侦听具有指定地址的令牌,该字段在 USB 复位事件发生时自动清除
enableWO8如果置 1,启用 USB 地址过滤
triggerWO9在下一个 EP0 IN 令牌完成时置位 enable(见上文),在任何 EP0 完成后由硬件清零

这里的想法是在 EP0 上收到 USB SET_ADDRESS 的设置 (setup) 数据包前,保持整个寄存器清零。此时,您可以设置地址和触发字段,然后向 EP0 提供 IN 零长度描述符以完成 SET_ADDRESS 序列。控制器将在该描述符完成时自动打开地址过滤。

中断 INTERRUPT (0xFF08)

该寄存器的每个位都可以通过写入‘1’来清除。读取该寄存器将返回当前中断状态。

名称类型描述
endpointsW1C15-0当端点产生中断时拉高
resetW1C16当 USB 复位发生时拉高
ep0SetupW1C17当端点 0 收到设置请求时拉高
suspendW1C18当发生 USB 挂起时拉高
resumeW1C19当 USB 发生恢复时拉高
disconnectW1C20当 USB 断开连接时拉高

暂停 HALT (0xFF0C)

该寄存器允许将单个端点置于休眠状态,以确保 CPU 操作的原子性,从而允许在端点寄存器和描述符上执行读/修改/写操作。如果 USB 主机在暂停启用且端点启用的情况下寻址给定端点,那么外设将返回NAK。

名称类型描述
endpointIdWO3-0想要进入休眠状态的目标端点
enableWO4当设置暂停时为活动状态,当清除时端点解除暂停。
effective enableRO5设置使能后,需要等待硬件本身设置该位,以保证原子性

配置 CONFIG (0xFF10)

名称类型描述
pullupSetSO0写入‘1’以使能 dp 引脚上的 USB 设备上拉
pullupClearSO1
interruptEnableSetSO2写入‘1’让当前和未来的中断发生
interruptEnableClearSO3

信息 INFO (0xFF20)

名称类型描述
ramSizeRO3-0内部 RAM 将有 (1 « this) 字节

端点 ENDPOINTS (0x0000 - 0x003F)

端点状态存储在内部 RAM 的开头,每个端点状态有一个 32 位字。

名称类型描述
enableRW0如果不设置,端点将忽略所有流量
stallRW1如果设置,端点将始终返回 STALL 状态
nackRW2如果设置,端点将始终返回 NACK 状态
dataPhaseRW3指定使用的出入数据 PID。‘0’ => DATA0。该字段也由控制器更新。
headRW15-4指定当前描述符头(链表)。 0 => 空列表,字节地址 = this « 4
isochronousRW16
maxPacketSizeRW31-22

要获得端点响应,需要:

• 将其使能标志设置为 1

那么有几种情况: 要么设置了 stall 或 nack 标志,所以控制器将始终响应相应的响应 -要么,对于 EP0 设置请求,控制器不会使用描述符,而是会将数据写入 SETUP_DATA 寄存器和 ACK -要么你有一个空链表 (head==0),在这种情况下它将响应 NACK -要么你至少有一个由 head 指向的描述符,在这种情况下,它将执行该描述符,并在一切顺利时进行 ACK设置数据 SETUP_DATA (0x0040 - 0x0047)当端点 0 接收到 SETUP 事务时,该事务的数据将存储在该位置。

描述符

描述符允许指定一个端点需要如何处理出入 (IN/OUT) 事务的数据阶段。它们存储在内部 RAM 中,可以通过链表链接在一起,并且需要在 16 字节边界上对齐。

名称描述
offset015-0指定当前传输进度(以字节为单位)
code019-160xF => 进行中, 0x0 => 成功
next115-4指向下一个描述符 0 => 无,字节地址 = this « 4
length131-16分配给数据字段的字节数
方向216‘0’ => 输出,’ 1’ => 输入
interrupt217如果置位,描述符完成时将产生中断。
comple
tionOn
Full
218通常,描述符补全只会在 USB 传输小于 maxPacketSize 时发生。但如果置位了
该字段,那么当描述符被填满时也被视为事件已完成。 (offset == length)
data1OnCopleti2on 19描述符完成时强制端点 dataPhase 为 DATA1
data

请注意,如果控制器接收到 IN/OUT 与描述符 IN/OUT 不匹配的帧,那么该帧将被忽略。

此外,要初始化描述符, CPU 应将代码字段设置为 0xF

用法

import spinal.core._
import spinal.core.sim._
import spinal.lib.bus.bmb.BmbParameter
import spinal.lib.com.usb.phy.UsbDevicePhyNative
import spinal.lib.com.usb.sim.UsbLsFsPhyAbstractIoAgent
import spinal.lib.com.usb.udc.{UsbDeviceCtrl, UsbDeviceCtrlParameter}
case class UsbDeviceTop() extends Component {
val ctrlCd = ClockDomain.external("ctrlCd", frequency = FixedFrequency(100 MHz))
val phyCd = ClockDomain.external("phyCd", frequency = FixedFrequency(48 MHz))
val ctrl = ctrlCd on new UsbDeviceCtrl(
p = UsbDeviceCtrlParameter(
addressWidth = 14
),
bmbParameter = BmbParameter(
addressWidth = UsbDeviceCtrl.ctrlAddressWidth,
dataWidth = 32,
sourceWidth = 0,
contextWidth = 0,
lengthWidth = 2
)
)
val phy = phyCd on new UsbDevicePhyNative(sim = true)
ctrl.io.phy.cc(ctrlCd, phyCd) <> phy.io.ctrl
val bmb = ctrl.io.ctrl.toIo()
val usb = phy.io.usb.toIo()
val power = phy.io.power.toIo()
val pullup = phy.io.pullup.toIo()
val interrupts = ctrl.io.interrupt.toIo()
}
object UsbDeviceGen extends App{
SpinalVerilog(new UsbDeviceTop())
}

4.USB OHCI

SpinalHDL 库中有 USB OHCi 控制器(主机)。

用几个要点总结支持的功能:

• 它遵循 OpenHCI USB 开放式主机控制接口规范 (OHCI)。

• 它已经与上游的 linux/uboot OHCI 驱动兼容。(tinyUSB 上也有 OHCI 驱动)

• 它提供了 USB 主机全速和低速功能(12Mbps 和 1.5Mbps)

• 在 linux 和 uboot 上测试过

• 一个可以承载多个端口(多至 16 个)的控制器

• 用于 DMA 访问的 Bmb 存储器接口

• 用于配置的 Bmb 内存接口

• 内部物理层需要一个时钟,该时钟需要为 12 Mhz 的倍数,至少 48 Mhz

• 控制器频率不受限制

• 无需外部物理层

经过测试且功能正常的设备:

• 大容量存储(ArtyA7 Linux 上约为 8 Mbps)

• 键盘/鼠标

• 音频输出

• 集线器

限制:

• 某些 USB 集线器(目前已有一个)对将低速设备连接至全速主机的模式不友好。

• 某些现代设备无法在 USB 全速上运行(例如: Gbps 以太网适配器)

• 需要与 CPU 保持内存一致性(或者需要 CPU 能够刷新驱动中的数据缓存)

部署

https://github.com/SpinalHDL/SaxonSoc/tree/dev-0.3/bsp/digilent/ArtyA7SmpLinux

https://github.com/SpinalHDL/SaxonSoc/tree/dev-0.3/bsp/radiona/ulx3s/smp

用法

import spinal.core._
import spinal.core.sim._
import spinal.lib.bus.bmb._
import spinal.lib.bus.bmb.sim._
import spinal.lib.bus.misc.SizeMapping
import spinal.lib.com.usb.ohci._
import spinal.lib.com.usb.phy.UsbHubLsFs.CtrlCc
import spinal.lib.com.usb.phy._
class UsbOhciTop(val p : UsbOhciParameter) extends Component {
val ohci = UsbOhci(p, BmbParameter(
addressWidth = 12,
dataWidth = 32,
sourceWidth = 0,
contextWidth = 0,
lengthWidth = 2
))
val phyCd = ClockDomain.external("phyCd", frequency = FixedFrequency(48 MHz))
val phy = phyCd(UsbLsFsPhy(p.portCount, sim=true))
val phyCc = CtrlCc(p.portCount, ClockDomain.current, phyCd)
phyCc.input <> ohci.io.phy
phyCc.output <> phy.io.ctrl
// propagate io signals
val irq = ohci.io.interrupt.toIo
val ctrl = ohci.io.ctrl.toIo
val dma = ohci.io.dma.toIo
val usb = phy.io.usb.toIo
val management = phy.io.management.toIo
}
object UsbHostGen extends App {
val p = UsbOhciParameter(
noPowerSwitching = true,
powerSwitchingMode = true,
noOverCurrentProtection = true,
powerOnToPowerGoodTime = 10,
dataWidth = 64, //DMA data width, up to 128
portsConfig = List.fill(4)(OhciPortParameter()) //4 Ports
)
SpinalVerilog(new UsbOhciTop(p))
}

  • 20
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千穹凌帝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值