基于Redd复合体制作USB总线分析仪的主管

在最后几篇文章中,我们看了Redd复合体的“固件”示例,该复合体的FPGA成为通用逻辑分析仪。然后,我希望下一步将其转变为USB总线分析仪。事实是这种类型的专用分析仪非常昂贵,我需要检查为什么相同的USB可以正常工作(如果连接到机器上)可以正常工作,并且如果在将所有设备都插入连接器的情况下打开机器,那它就无法工作。也就是说,软件分析器无法在此应对。在我写作时,我不知何故被带走了,写了五篇文章。现在我们可以说,它们不仅显示了分析仪本身,而且还显示了以“快速”模式创建分析仪的典型过程。本文将向您展示如何不仅基于Redd,而且还基于现成的面包板制造这种分析仪,可以在Ali Express上购买。









也许,今天我什至会打破传统,并不会在Redd复杂系统上而是在常规布局上调试项目。首先,我知道绝大多数读者都无法使用这种复杂的软件,但是他们确实可以使用Ali Express。嗯,其次,我懒得用双USB设备和主机连接起来围起来一个花园,而且还无法应付新出现的干扰。



早在2017年,我就在网络上寻找现成的解决方案,发现了一件如此美妙的事情,或者更确切地说是它的祖先。现在他们将所有东西都放在一个专门的板上,但是到处都有Xilinx的简单面包板的照片,WaveShare的板与之相连(您可以在此处了解有关内容)。让我们看一下该板的照片。







它一次具有两个USB连接器。此外,该图显示它们已并行化。您可以将USB设备插入A型插座,也可以将电缆连接到微型USB连接器,我们将其插入主机。 OpenVizsla项目的说明说这种方式有效。唯一可惜的是,该项目本身很难阅读。您可以在github上使用它,但我不会提供指向页面上指示的帐户的链接,无论如何大家都会找到它,但是它已被MiGen重做,而是我在2017年找到的版本:http:// github。 com /超嵌入式/核心,它在干净的Verilog上,并且有usb_sniffer分支。在那里,所有事情都不直接通过ULPI进行,而是通过ULPI到UTMI转换器进行(这两个不雅词都是与高速USB 2.0通道以及处理器和FPGA可以理解的总线相匹配的物理级微电路),然后才可以与此UTMI一起使用。那里的一切运作方式,我还没有弄清楚。因此,我宁愿从头开始开发,因为我们很快就会看到那里的一切令人恐惧而不是困难。



您可以使用什么硬件



标题中问题的答案很简单:在具有FPGA和外部存储器的任何人上。当然,在本系列文章中,我们将仅考虑Altera FPGA(英特尔)。但是,请记住,来自ULPI微电路(位于该手帕上)的数据以60 MHz运行。此处不接受长电线。将CLK线连接到GCK组的FPGA输入上也很重要,否则一切都会起作用,然后失败。最好不要冒险。我不建议您以编程方式转发它。我试过了。所有这些都以GCK组的一条腿的金属丝结束。



对于今天的实验,应我的要求,一个朋友给我焊接了这样的系统:







带FPGA和SDRAM的微模块(在ALI上查找,用短语FPGA AC608表示))和WaveShare的同一ULPI板。这就是模块在其中一位卖家的照片中显示的方式。我太懒了,无法从机箱上拧开它:







顺便说一句,就像我的机箱照片一样,通风孔非常有趣。在模型上,绘制一个实体层,然后在切片器中设置40%的填充,并说您需要从底部到顶部制作零个实体层。结果,3D打印机会自己绘制通风孔。非常舒适。

通常,查找硬件的方法很明确。现在我们开始设计分析仪。相反,在前两篇文章中我们已经完成了分析仪的工作在这里我们使用硬件在这里-可以使用它),现在我们将简单地设计一个面向问题的探头,以捕获来自ULPI微电路的数据。



头部应该能做什么



就逻辑分析仪而言,一切都很简单。有数据。我们连接到它们并开始打包,然后将它们发送到AVALON_ST总线。这里的一切都更加复杂。 ULPI规范可在此处找到。九十三张无聊的文字。就个人而言,这使我感到沮丧。 WaveShare板上安装的USB3300芯片的说明看起来更加简单。你可以在这里得到。尽管自2017年12月以来我仍然积蓄了勇气,但有时我会感到沮丧,因此有时会阅读并立即关闭文档。



从描述中可以明显看出,ULPI具有一组必须在开始工作之前填写的寄存器。这主要是由于上拉电阻和端接电阻。这是一张图片来说明这一点:







根据角色(主机或设备)以及所选的速度,必须包括不同的电阻器。但是我们既不是主机,也不是设备!我们必须断开所有电阻,以免干扰总线上的主要设备!这是通过写入寄存器来完成的。



好,速度快。必须选择工作速度。为此,您还需要写入寄存器。



配置完所有内容后,您就可以开始获取数据了。但是以ULPI的名义,字母“ LP”表示“低引脚数”。腿数的这种非常减少导致了如此激烈的协议,只是坚持下去!让我们仔细看看该协议。



ULPI协议



对于普通人来说,ULPI协议有点不寻常。但是,如果您坐在文档旁打坐,那么就会出现一些或多或少可以理解的功能。显然,开发人员已尽一切努力来真正减少使用的联系人数量。



我不会在此处重新键入完整的文档。让我们将自己限制在最重要的事情上。其中最重要的是信号的方向。记住它是不可能的,最好每次查看图片:







ULPI LINK是我们的FPGA。



数据接收时序图



静止时,我们必须向数据总线发出一个常量0x00,它对应于IDLE命令。如果数据来自USB总线,则交换协议将如下所示:







循环将以DIR信号最多飞到一个的事实开始。首先,它将有一个时钟周期,以便系统有时间切换数据总线的方向。进一步-经济奇迹开始了。看到NXT信号的名称了吗?从我们这里传送时意味着NEXT。这是一个完全不同的信号。当DIR为1时,我将称为NXT C / D。低级别-我们有一个团队。高数据。



也就是说,我们必须始终在高DIR上固定9位(DATA总线和NXT信号)(然后通过软件对第一个时钟进行滤波),或者从DIR起飞后的第二个时钟开始固定。如果DIR线降为零,我们将数据总线切换为写入状态,然后再次开始广播IDLE命令。



通过数据接收-很明显。现在,让我们分析寄存器的工作。



写入ULPI寄存器的时序图



要写入寄存器,请使用以下临时房屋(我故意切换到行话,因为我觉得我倾向于GOST 2.105,这很无聊,所以我







将其移开):首先,我们必须等待状态DIR = 0。在时钟T0,我们必须在数据总线上将TXD CMD设置为常数。这是什么意思?您无法马上弄清楚,但是如果您仔细阅读文档,结果可以在此处找到所需的值:







也就是说,高数据位应设置为值“ 10”(对于整个字节,掩码为0x80),而低位应设置为寄存器号。



接下来,您应该等待NXT信号发出。有了这个信号,微电路确认它听到了我们的声音。在上图中,我们在时钟T2等待它,并在下一个时钟(T3)上设置数据。在时钟T4上,ULPI将接收数据并删除NXT。我们将在STP中标记单位交换周期的结束。同样在T5上,数据将被锁存到内部寄存器中。该过程已结束。这是为数不多的结论的回报。但是我们只需要在启动时就写入数据,因此,当然,我们将不得不遭受开发的困扰,但是这不会特别影响工作。



从ULPI寄存器读取的时序图



老实说,对于实际任务,读取寄存器不是那么重要,但让我们也来看看。阅读至少对确保我们正确实施记录有用。







我们看到在我们面前是前两个临时房屋的爆炸性混合物。我们按照写入寄存器的方式设置地址,然后根据读取数据的规则获取数据。



好?让我们开始设计一个自动机,它将为我们塑造这一切吗?



头部结构图



从上面的描述中可以看到,磁头必须一次连接到两条总线:AVALON_MM用于访问寄存器,而AVALON_ST用于发布要存储在RAM中的数据。头部的主要是大脑。因此,它应该是一个状态机,它将生成我们之前考虑的时间图。







让我们从接收数据的功能开始其开发。这里应该记住,我们不能以任何方式影响来自ULPI总线的流量。从那里开始的数据,如果它开始运行,它将继续运行。他们不在乎AVALON_ST总线是否准备就绪。因此,我们将简单地忽略总线的不可用性。在真实的分析仪中,有可能在数据输出没有准备就绪的情况下添加警报指示。在本文的框架内,所有内容都应该很简单,因此让我们在将来记住这一点。为了确保总线的可用性,就像在逻辑分析仪中一样,我们将有一个外部FIFO模块。总的来说,用于接收数据流的自动机的过渡图如下:







DIR起飞-开始接收。我们在wait1中挂了一个时钟,然后在DIR等于1时接受它。降为零-经过一个时钟(尽管不是必需的事实,但现在我们将状态wait2设置为空闲)返回空闲状态。



到目前为止,一切都很简单。不要忘记,不仅D0_D7线,而且NXT线都应连接到AVALON_ST总线,因为它确定了现在正在传输的内容:命令或数据。



寄存器写周期可能具有不可预测的执行时间。从AVALON_MM总线的角度来看,这不是很好。因此,我们将使其更加棘手。让我们创建一个缓冲寄存器。数据将进入其中,之后将立即释放AVALON_MM总线。从正在开发的自动机的角度来看,出现have_reg输入信号(已接收到寄存器中的数据,应发送该数据)和reg_served输出信号(这意味着寄存器发布过程已完成)。将写入逻辑添加到自动机的过渡图上的寄存器。







我用红色突出显示了DIR = 1条件,以表明它具有最高优先级。这样就可以排除机器新分支中DIR信号为零的期望。根本无法登录到具有不同值的分支。 SET_CMDw状态为蓝色,因为它最有可能是纯虚拟的。这些只是要执行的动作!没有人会费心在数据总线上以及在过渡期间设置相应的常数!除其他外,在STPw状态下,还可将reg_served信号锁定一个时钟周期,以清除AVALON_MM总线的BSY信号,从而允许一个新的写周期。



好了,仍然需要添加一个分支来读取ULPI寄存器。在这里,情况恰恰相反。公交服务机​​向我们发送请求,并等待我们的回复。接收到数据后,他就可以对其进行处理。它可以与总线暂停或轮询一起使用,这些已经是该机器的问题。今天,我决定进行一项调查。正在请求数据-出现了BSY。BSY如何消失-您可以接收读取的数据。总体而言,该图采用以下形式:







也许在开发过程中会进行一些调整,但是现在,我们将坚持使用此图。毕竟,这不是报告,而是有关开发方法的说明。而且这种技术使得您首先需要绘制一个过渡图,然后-根据此图进行逻辑调整,以针对弹出的细节进行调整。



从AVALON_MM端实现自动机的功能



使用AVALON_MM总线时,可以采用两种方式。首先是创建总线访问延迟。我们在上一篇文章中探讨了这种机制,我警告说它充满了问题。第二种方式是经典。输入状态寄存器。在事务开始时,设置BSY信号,完成时将其复位。并将所有事情分配给总线主控逻辑(Nios II处理器或JTAG桥接器)。每个选项都有其自身的优点和缺点。由于我们已经完成了带有总线延迟的变体,所以让我们今天通过状态寄存器进行所有更改。



我们设计主机



我想引起您注意的第一件事是我最喜欢的RS触发器。我们有两台机器。第一个服务于AVALON_MM总线,第二个服务于ULPI接口。我们发现它们之间的连接要经过几个标志。每个标志只能写入一个进程。每个自动机由其自己的过程实现。怎样成为?一段时间以来,我刚刚开始添加RS触发器。我们有两个位,因此它们必须由两个RS触发器生成。他们来了:

//   
always_ff @(posedge ulpi_clk)
begin
      //    
      if  (reg_served)
           write_busy <= 0;
      else if (have_reg)
           write_busy <= 1;

      //    
      if  (read_finished)
           read_busy <= 0;
      else if (reg_request)
           read_busy <= 1;
end


一只进程的公鸡已送达,第二只公鸡的have_reg。RS触发器在其自身的过程中根据其自身生成write_busy信号。同样,read_busy由read_finished和reg_request组成。您可以采取不同的方法,但是在创作路径的这个阶段,我喜欢这种方法。



这就是设置BSY标志的方式。黄色代表写作过程,蓝色代表阅读过程。Verilogov过程具有一个非常有趣的功能。在其中,您可以分配值不是一次,而是几次。因此,如果我希望信号在一个时钟周期内起飞,则在过程开始时将其无效(我们看到两个信号都在那里无效),并根据在一个时钟周期内执行的条件将其设置为1。输入条件将覆盖默认值。在所有其他情况下,它将起作用。因此,向数据端口写入会在一个时钟周期内启动have_reg信号的输出,而向控制端口写入位0将向reg_request信号的输出启动。





相同的文字。
//  AVALON_MM  
always_ff @(posedge ulpi_clk)
begin
   //    ,    
   //      
   have_reg    <= 0;
   reg_request <= 0;

   if (write == 1) 
   begin
      case (address)
          0 : addr_to_ulpi <= writedata [5:0];
          //       
          1 : begin
                data_to_ulpi <= writedata [7:0];
                have_reg <= 1;
              end
          2 : begin
                //      
                reg_request <= writedata[0];
		force_reset = writedata [31];
              end
         3: begin end
      endcase
   end
end   






如上所述,一个时钟周期足以将相应的RS触发器设置为1。从这一刻起,已设置的BSY信号开始从状态寄存器中读取:





相同的文字。
//  AVALON_MM  
always_comb 
begin
   case (address)
      //   (  )
      0 : readdata <= {26'b0, addr_to_ulpi};

      //  
      1 : readdata <= {23'b0, data_from_ulpi};

      // 2 -  ,   -   

      //  
      3 : readdata <= {30'b0, (reg_request | read_busy), (have_reg | write_busy)};
      default: readdata <= 0;
   endcase
end   






实际上,自然而然地,我们熟悉了AVALON_MM总线上服务的进程。

让我也提醒您有关使用ulpi_data总线的原则。该总线是双向的。因此,您应该使用一种标准的技术来处理它。这是声明相应端口的方式:

   inout        [7:0]  ulpi_data,


我们可以从该总线读取,但不能直接写入。相反,我们为记录创建一个副本。

logic [7:0] ulpi_d = 0;


然后,通过以下多路复用器将此副本连接到主总线:

//      inout-
assign ulpi_data = (ulpi_dir == 0) ? ulpi_d : 8'hzz;


我试图在Verilog代码中尽可能多地评论主机的逻辑。正如我在过渡图的开发过程中所期望的那样,在实际实现中,逻辑有所改变。一些州被排除在外。不过,通过比较图形和源文本,我希望您了解在那里所做的一切。因此,我不会谈论这台机器。最好根据实际实验的结果提供与修改前相关的模块全文作为参考。

模块的全文。
module ULPIhead
(
   input               reset,
   output              clk66,

   // AVALON_MM
   input        [1:0]  address,
   input               write,
   input        [31:0] writedata,
   input               read,
   output logic [31:0] readdata = 0,

   // AVALON_ST
   input  logic        source_ready,
   output logic        source_valid = 0,
   output logic [15:0] source_data = 0,

   // ULPI
   inout        [7:0]  ulpi_data,
   output logic        ulpi_stp = 0,
   input               ulpi_nxt,
   input               ulpi_dir,
   input               ulpi_clk,
   output              ulpi_rst
);

logic      have_reg = 0;
logic      reg_served = 0;
logic      reg_request = 0;
logic      read_finished = 0;
logic [5:0] addr_to_ulpi;
logic [7:0] data_to_ulpi;
logic [7:0] data_from_ulpi;

logic      write_busy = 0;
logic      read_busy = 0;

logic [7:0] ulpi_d = 0;

logic force_reset = 0;

//   
always_ff @(posedge ulpi_clk)
begin
      //    
      if  (reg_served)
           write_busy <= 0;
      else if (have_reg)
           write_busy <= 1;

      //    
      if  (read_finished)
           read_busy <= 0;
      else if (reg_request)
           read_busy <= 1;
end

//  AVALON_MM  
always_comb 
begin
   case (address)
      //   (  )
      0 : readdata <= {26'b0, addr_to_ulpi};

      //  
      1 : readdata <= {23'b0, data_from_ulpi};

      // 2 -  ,   -   

      //  
      3 : readdata <= {30'b0, (reg_request | read_busy), (have_reg | write_busy)};
      default: readdata <= 0;
   endcase
end   

//  AVALON_MM  
always_ff @(posedge ulpi_clk)
begin
   //    ,    
   //      
   have_reg    <= 0;
   reg_request <= 0;

   if (write == 1) 
   begin
      case (address)
          0 : addr_to_ulpi <= writedata [5:0];
          //       
          1 : begin
                data_to_ulpi <= writedata [7:0];
                have_reg <= 1;
              end
          2 : begin
                //      
                reg_request <= writedata[0];
		force_reset = writedata [31];
              end
         3: begin end
      endcase
   end
end   

//   
enum {idle,
wait1,wr_st,
wait_nxt_w,hold_w,
wait_nxt_r,wait_dir1,latch,wait_dir0

} state = idle;
always_ff @ (posedge ulpi_clk)
begin
   if (reset)
   begin
       state <= idle;
   end else
   begin
      //    
      source_valid <= 0;
      reg_served  <= 0;
      ulpi_stp <= 0;
      read_finished <= 0;
      case (state)
      idle: begin
           if (ulpi_dir)
               state <= wait1;
           else if (have_reg) 
                begin
                  //      , 
                  //    ,   
                  // 
                  ulpi_d [7:6] <= 2'b10;
                  ulpi_d [5:0] <= addr_to_ulpi;
                  state <= wait_nxt_w;
                end
           else if (reg_request)
                begin
                  //  -   
                  ulpi_d [7:6] <= 2'b11;
                  ulpi_d [5:0] <= addr_to_ulpi;
                  state <= wait_nxt_r;
                end
         end
      //      TURN_AROUND
      wait1 : begin
            state <= wr_st;
            //    ,   
            source_valid <= 1; 
            source_data <= {7'h0,!ulpi_nxt,ulpi_data};
         end
      //     DIR -    AVALON_ST
      wr_st : begin
            if (ulpi_dir)
            begin
              //   ,    
               source_valid <= 1;
               source_data <= {7'h0,!ulpi_nxt,ulpi_data};
            end else
               //      wait2,
               //   ,   - . 
               state <= idle;
         end
      wait_nxt_w : begin
           if (ulpi_nxt)
           begin
              ulpi_d <= data_to_ulpi;
              state <= hold_w;
           end
         end
      hold_w: begin
           //   ,  ULPI 
           //     .   NXT
           //  ...
           if (ulpi_nxt) begin
              // ,  AVALON_MM    
              reg_served  <= 1;
              ulpi_d <= 0;    //   idle
              ulpi_stp <= 1;  //     STP
              state <= idle;  //   -    idle
           end
         end
       //   STPw   ...
       // ...
      //    . ,   NXT
      //    ,    
      wait_nxt_r : begin
           if (ulpi_nxt)
           begin
              ulpi_d <= 0;    //    
              state <= wait_dir1;
           end
         end
      // ,    
      wait_dir1: begin
          if (ulpi_dir)
             state <= latch;
        end
      //    
      //   -   
      latch: begin
          data_from_ulpi <= ulpi_data;
          state <= wait_dir0;
        end
      // ,     
      wait_dir0: begin
          if (!ulpi_dir)
          begin
             state <= idle;
             read_finished <= 1;
          end
        end
   
      default:	begin
         state <= idle;
         end
      endcase
    end
end
//      inout-
assign ulpi_data = (ulpi_dir == 0) ? ulpi_d : 8'hzz;

// reset   ,      
assign ulpi_rst = reset | force_reset;

assign clk66 = ulpi_clk;

endmodule




程序员指南



ULPI寄存器地址端口(+0)



ULPI总线寄存器的地址应放在偏移量为+0的端口中,该地址将用于工作



ULPI寄存器数据端口(+4)



写入该端口时:将自动开始写入ULPI寄存器的过程,该过程的地址已在寄存器地址的端口中设置。在之前的写操作完成之前,禁止对该端口进行写操作。



读取时:此端口将返回从ULPI寄存器的最后一次读取中获得的值。



ULPI控制端口(+8)



读数始终为零。写入的位分配如下:



位0-当写入单个值时,启动读取ULPI寄存器的过程,该寄存器的地址在ULPI寄存器的地址端口中设置。



位31-写入1时,将复位信号发送到ULPI芯片。



其余位保留。



状态端口(+ 0x0C)



只读。



位0-WRITE_BUSY。如果等于1,则正在写入ULPI寄存器。



位1-READ_BUSY。如果等于1,则正在进行ULPI寄存器的读取过程。



其余位保留。



结论



我们熟悉了USB分析仪头的物理组织方法,设计了用于ULPI微电路的基本自动机,并为该头实现了SystemVerilog模块草案。在下面的文章中,我们将研究建模过程,模拟此模块,然后对其进行实际实验,根据其结果,我们将最终确定代码。也就是说,到最后,我们至少还有四篇文章。



All Articles