问答

汇集网友智慧,解决技术难题

21ic问答首页 - 沁恒微CH132C的ULPI时序问题

时序

沁恒微CH132C的ULPI时序问题

Tipasig2026-09-22
我使用FPGA实现一个USB 2.0高速设备控制器逻辑,如果USB PHY芯片使用USB3343,则功能一切正常(高速握手,枚举,Bulk通信);但切换到沁恒微的CH132C之后,枚举总是失败。
请各路大神提供解决问题思路,有认识沁恒微技术支持的朋友也麻烦给我支个招。

使用USB协议分析仪捕获如下,详见附图:
1. 高速握手正常,说明Device向USB PHY芯片能正常写入FUNC_CTRL寄存器;
2. 获取设备描述符总是失败在SETUP事务里,Host下发SETUP和DATA0包正常,但Device没有回复ACK包;
3. 在FPGA加ILA观测ulpi_dir_i, ulpi_nxt_i为何Device没收到SETUP或DATA0包,这时设备描述符的获取又能变正常,但获取其它描述符还是有类似问题,包括再次获取设备描述符也可能失败。

怀疑是ULPI时序问题。
1. CH132C使用输出ULPI时钟模式,即XI输入12MHz,CLK输出60MHz给FPGA;
  FPGA对该时钟也加了BUFG;
  时序报告也没有违例。
2. 比较CH132C和USB3343手册,PHY芯片输出方向(USB OUT方向)的DIR,NXT的输出延迟tDC,以及Data的输出延迟tDD,两个芯片有稍许差异,CH132C的tDC是min=0.8ns, max=6.0ns; tDD是min=0.8ns, max=8.0ns;而USB3343的tDC和tDD都是min=1.5ns,max=6.0ns。
3. 于是综合FPGA时再加如下时序约束,但问题还是没有根本解决。

#PHY->Link方向 加set_input_delay



# Control lines Output Delay: tDC=(0.8ns, 6.0ns)
set_input_delay -clock usb_clk -max 6.0 [get_ports {ulpi_dir_i ulpi_nxt_i}]
set_input_delay -clock usb_clk -min 0.8 [get_ports {ulpi_dir_i ulpi_nxt_i}]
# Data lines Output Delay: tDD=(0.8ns, 8.0ns)
set_input_delay -clock usb_clk -max 8.0 [get_ports {ulpi_data_io
  • }]
    set_input_delay -clock usb_clk -min 0.8 [get_ports {ulpi_data_io
  • }]


    #PHY<-Link方向 加set_output_delay

    # STP:tSC=6.0ns tHC=0ns
    set_output_delay -clock usb_clk -max -6.0 [get_ports {ulpi_stp_o}]
    set_output_delay -clock usb_clk -min  0.0 [get_ports {ulpi_stp_o}]
    # DATA:tSD=6.0ns tHD=0ns
    set_output_delay -clock usb_clk -max -6.0 [get_ports {ulpi_data_io
  • }]
    set_output_delay -clock usb_clk -min  0.0 [get_ports {ulpi_data_io
  • }]





  • 回答 +关注 2
    1688人浏览 3人回答问题 分享 举报
    3 个回答
    • 承接前面的全速USB串口设备和高速ULPI接口的调试过程,我们继续完成了下面的工作。

                                     
      登录/注册后可看大图

      USB协议分析仪使用集锦之三:克隆一个USB Sniffer


                                     
      登录/注册后可看大图


      参考对象位于https://github.com/ataradov/usb-sniffer.git,这是一个用Cypress FX2 MCU实现的USB高速设备,USB捕捉功能用Lattice CPLD加Microchip USB PHY芯片实现。
      现在用一颗低成本的国产FPGA加USB PHY芯片,来代替Cypress MCU和Lattice CPLD。FPGA用全数字电路实现USB设备控制器、USB标准传输和厂商传输、以及CPLD中的USB捕捉功能,不再需要MCU及其固件。


                                     
      登录/注册后可看大图

      • 克隆第一步,获取该设备的各种USB描述符
      • 实例化USB设备控制器IP
      • Copy&Paste usb-sniffer.git里面的CPLD数字逻辑
      • 上FPGA综合验证

                                       
        登录/注册后可看大图



      1. 克隆第一步,获取该设备的各种USB描述符。
      usb-sniffer.git的固件是开源的,其USB描述符可以从源码里扒出来。但这里使用USB协议分析仪抓取枚举过程来获取其USB描述符,这种方法更有普适性以克隆任何USB 2.0设备。图1是抓取的USB Sniffer的枚举过程:


      648396ae84ca429bcd.jpg

      这是一个USB高速(HS)设备,共有8个USB描述符:
      * 设备描述符 (USB Device Descriptor)
      * 配置描述符 (USB Configuration Descriptor)
      * BOS描述符 (Binary Device Object Store Descriptor)
      * 微软OS描述符集 (Microsoft OS Descriptor Set)
      * 4个字符串描述符 (Language ID, Manufacture, Product, Serial Number)
      其中BOS描述符和微软OS描述符集是在Windows操作系统上实现免驱的关键。


                                     
      登录/注册后可看大图


      1.1 复制USB设备描述符。

      351846ae84d047cc9b.jpg

      如图2所示,在"USB概览"和"字段细节"视图里选中"GetDescriptor (Device)";
      在"原始数据"视图里右击鼠标,并选中"Copy as Hex Array",
      即在系统剪贴板里复制了USB设备描述符:
      1. { 0x12, 0x01, 0x10, 0x02, 0x00, 0x00, 0x00, 0x40, 0x66, 0x66, 0x20, 0x66, 0x00, 0x01, 0x01, 0x02, 0x03, 0x01 }

      这里的关键字段除了idVendor和idProduct之外,还有
      • bcdUSB: 作为BCD码,不能是0x0200 (2.0), 否则后面的微软OS描述符集将失效,这是无脑克隆的好处。
      • bMaxPacketSize0: 硬件实现的USB设备控制器EP0的FIFO深度为64字节。

                                       
        登录/注册后可看大图



      1.2 继续复制其它USB描述符。


      图3是USB配置描述符,其内部包含了USB接口描述符和USB端点描述符。接口描述符指明USB Sniffer是一个厂商自定义的USB类设备,端点描述符定义了一个端点:EP2是Bulk IN,其FIFO深度为512字节。

      711206ae84db660187.jpg


      图4是USB BOS描述符,其设备能力由Platform(这里是微软)定义,具体来说就是将引入后面的MS OS 2.0 Descriptor Set.

      932946ae84dd51adaa.jpg


      图5是微软OS描述符集,注意这不是USB规范定义的描述符,而是厂商微软自定义的描述符,从其Setup请求类型Vendor就可以看出来。微软OS描述符集也分版本: 1.0和2.0, 这里的Setup请求wIndex=7指示的是Microsoft OS 2.0 Descriptor Set. 我们无脑克隆这个微软OS描述符集就行,用于Windows OS的USB设备免驱。这个描述符集的内容很长,具体字段可以参考Tipasig USB协议分析仪的解析。

      496086ae84df260117.jpg


      图6是USB字符串描述符,Setup请求里的wValue=0x0302, 其中字节0x03是字符串描述符,字节0x02是字符串索引指向产品名称(0=wLANGID, 1=iManufacture, 2=iProduct, 3=iSerialNumber)。另外USB里的字符串都是UTF16编码, 即ASCII 'U'在字符串描述符里编码为'U', 0x00; ASCII 'r'编码为'r', 0x00; ...

      759606ae84e07ce884.jpg

                                     
      登录/注册后可看大图


      1.3 粘贴USB描述符进USB设备控制器IP
      如图7所示,"descriptors"是收集后的USB Sniffer设备的所有描述符,这个将作为参数传给USB设备控制器IP.
      245786ae84e7827614.jpg

                                     
      登录/注册后可看大图


      2. 实例化USB设备控制器IP
      下面的代码展示了USB设备控制器IP的配置参数,除了descriptors,还有bus和speed。

      1. <div>  /* Monitor side. */
      2.   val monitor = new UsbDevice(
      3.     descriptors,  //Endpoints number, type, bMaxPacketSize
      4.     bus  ="ulpi", //utmi:      testbench
      5.                       //ulpi:      USB PHY chip
      6.                      //native_fs: SoftPHY at full speed
      7.                     //native:    SoftPHY
      8.     speed="high"  //high:      High Speed at 480Mbps
      9.                          //full:      Full Speed at 12Mbps
      10.                         //low:       Low  Speed at 1.5Mbps
      11.   )</div>

      descriptors不仅定义了该设备的端点数量,类型,FIFO深度,还将被实例化成ROM block供枚举使用;

      bus="ulpi"指明总线接口使用ULPI,即实际的USB PHY芯片;
      speed="high"指明该设备的Monitor端口实现为USB高速设备。
      该IP用SpinalHDL实现,这里不展开,只是展示其容易配置的特点。

      注意,还要实现一个厂商USB控制传输的逻辑。
      1. case class SnifferVendorRequestHandler() extends ControlRequestHandler

                                     
      登录/注册后可看大图


      3. Copy&Paste usb-sniffer.git里面的CPLD数字逻辑
      下图展示了如何集成Lattice CPLD里面的UsbCapture组件(模块)。
      266846ae8503f9ce65.jpg
      注意,系统里有两个时钟域,monitorClockDomain是USB设备控制器IP工作的时钟域(其60MHz由FPGA生成),targetClockDomain是UsbCapture模块工作的时钟域(其60MHz由USB3343芯片生成)。这两个实例化的模块之间,是一个跨时钟域的异步FIFO.



                                     
      登录/注册后可看大图


      4. 上FPGA综合验证
      下面是FPGA的top文件,实际综合和验证过程略。

      1. <div>module top(
      2.    input wire          sys_clk
      3.   ,input wire          sys_rst_n

      4.   /* Target side w/ USB3343. */
      5.   ,output              t_usb_refclk
      6.   ,input               t_usb_clkout
      7.   ,output              t_usb_stp_o
      8.   ,input               t_usb_dir_i
      9.   ,input               t_usb_nxt_i
      10.   ,inout      [7:0]    t_usb_d_io
      11.   /* Target side w/ voltage comparators. */
      12.   ,input      [2:0]    cmp_dm_i
      13.   ,input      [2:0]    cmp_dp_i

      14.   /* Monitor port w/ CH132C. */
      15.   ,output              m_usb_rst_n
      16.   ,output              m_usb_xi
      17.   ,output              m_usb_clkout
      18.   ,output              m_usb_stp_o
      19.   ,input               m_usb_dir_i
      20.   ,input               m_usb_nxt_i
      21.   ,inout      [7:0]    m_usb_d_io

      22.   ,output wire         led_green
      23.   ,output wire         led_red
      24. );</div>


      最终我们获得了更低成本的USB Sniffer设备,其兼容Wireshark以及本文的USB协议分析仪。

                                     
      登录/注册后可看大图







    • 这里再补充一个ULPI的时序问题的调试过程。

      其实这不是ULPI硬件时序问题,而是ULPI时序控制的数字逻辑bug。

      废话少说,先用USB协议分析仪抓包上图看错误:
      708276ad1955915190.png
      这里FPGA实现的是一个USB串口全速设备PL2303,速度握手正常,主机开始枚举设备;
      结果在首个USB事务里,设备的首个回包ACK包就发生了严重错误,ACK包怎么这么长?

      USB协议分析仪抓取实物PL2303的正确枚举截图里,ACK包只有一个字节:
      316516ad196c34ae84.png

      速度握手正常(D+线上拉),这意味着FPGA作为Link,经过ULPI接口向PHY写寄存器是正确的。
      因为FUNC_CTRL寄存器里的xcvr_select, term_select, opmode错误的话,PHY是不会上拉D+线的。
      既然ULPI写寄存器时序正确,那为什么ULPI写USB包就错误了呢?

      Thinking...
      1. 是不是ULPI读包时序错误,即Device收到Host的SETUP包或者DATA0包错误,那么Device就乱发?
      不。因为Device发出0xD2字节,这意味着它就是收包正确,然后企图回ACK包,只是多发字节了。

      2. 查看ULPI规范,比较Figure 11 - USB data transmit (PID)和Figure 21 - Register Write的时序图,
      data, dir, stp, nxt的要求几乎一样。差别是,前者的PHY会拉高nxt直到Link拉高stp,即Link通过stp来停止PHY的nxt请求;
      后者是PHY只会拉高nxt信号2拍(1拍寄存器地址,1拍寄存器数据),stp有没有可能无所谓。
      238486ad1e2cf1361e.png
      91026ad1e34b697cc.png
      原因貌似明确了,Link(FPGA)的stp控制错误(早发出了1拍,或者丢失),导致PHY错过了stp了并一直输出nxt。
      这样总线data上的数据(不定态)也一直被PHY收到,直到PHY收到数据要吐出给Link,发出dir为止。
      方向有了,怎样进一步调试呢?

      3. 但在FPGA上不好确认这一点,因为ILA不方便设置触发条件去定位错误。
      于是还是修改testbench,增加ulpi phy的行为模型,替换原来的utmi行为模式,重新跑USB枚举过程的仿真:
      331426ad1ec571fbb5.png
      仿真定位到错误了,写PHY寄存器的stp信号正常,但写USB包(ACK)的stp信号缺失。
      那就拉更多的信号出来调试呗。

      4. 调试过程略。下面是枚举过程仿真通过的波形,写USB包(ACK)的stp信号正常的停止了PHY的nxt。
      431016ad1eab85a198.png

      最后是上FPGA跑通了PL2303串口。
      分享一下USB协议分析仪抓取的整个枚举过程,分享一下我的喜悦!
      350616ad1eec40cc81.png



    • 向大家汇报一下进展。
      把CH132C的ULPI时钟模式从输出改为输入,即FPGA PLL生成60MHz给CH132C的CLK管脚后,问题得到了解决。
      USB协议分析仪枚举成功截图祝贺 131066ab397e6caa47.png

      另,更新后的时钟约束为:
      # CH132C
      create_clock -name m_usb_clk -period 16.667 -waveform {0 8.334} [get_ports {m_usb_clkout}]

      # ========= PHY输出时钟抖动 =========
      # 容差0.03MHz/60MHz ~ +-0.00833ns
      # Jitter            ~ ?
      set_clock_uncertainty -setup 0.15 [get_clocks m_usb_clk]
      set_clock_uncertainty -hold  0.15 [get_clocks m_usb_clk]

      # ========= 输出方向约束(Link -> PHY)=========
      # STP  : tSC=6.0ns tHC=0ns (ULPI Clock Output Mode)
      # DATA : tSD=6.0ns tHD=0ns
      #            2.5       1.0 (ULPI Clock Input  Mode)
      set_output_delay -clock m_usb_clk -max -2.5 -min 1.0 [get_ports {m_usb_stp_o}]
      set_output_delay -clock m_usb_clk -max -2.5 -min 1.0 [get_ports {m_usb_d_io}]

      # ========= 输入方向约束(PHY -> Link)=========
      # DIR,NXT : tDC=(0.8ns, 6.0ns) (ULPI Clock Output Mode)
      #                1.5    6.5    (ULPI Clock Input  Mode)
      set_input_delay -clock m_usb_clk -min 1.5 -max 6.5 [get_ports {m_usb_dir_i m_usb_nxt_i}]
      # Data    : tDD=(0.8ns, 8.0ns)
      #                1.5    7.0
      set_input_delay -clock m_usb_clk -min 1.5 -max 7.0 [get_ports {m_usb_d_io}]

    您需要登录后才可以回复 登录 | 注册