我的“ Hello World!” 在FPGA或UART的下一版本上

在FPGA或UART的下一版本上

最终,我动手研究了FPGA。 但是以某种方式证明是错误的:我为Linux的硬件编写驱动程序,为微控制器编程,阅读电路(并做了一些设计),我需要进一步发展。

由于在我看来使LED闪烁并不有趣,所以我决定做一个简单的事情。 即,编写用于UART的接收器和发送器模块,将它们组合到FPGA内(同时了解如何使用IP内核),并在真实硬件上对其进行全部测试。

我马上说,成为任务的通用参数化核心不是一项任务。 这只是一个测试项目,主题是“感受一下FPGA是什么以及如何与之通信”。

因此,让我们从接收器开始。 该算法已被很好地描述 ,因此在这里我仅重复其要点。

  • RX信号采样频率是所需UART传输速率的四倍。
  • 如果当前未进行接收,则开始接收的条件被认为是输入信号RX从高电平到低电平的转变。
  • 可靠地标识起始位的条件被认为是在采样频率的第二个时钟周期将RX信号保持在低电平状态。 同时,我们实际上到达了位脉冲的中间,这将使我们能够每4个周期进一步采样脉冲。
  • 如果起始位或停止位有错误,请设置错误信号错误。 基于此,我们形成快速同步信号,将来将用于快速接收器同步。
  • 确定起始位后,我们从最小的位开始依次接收数据位。 接收到的数据向右移一位,写入寄存器。 接收结束的条件是在移位寄存器的第0位检测起始位。
  • 接收器的快速同步在于当RX信号变为高电平时检测到错误后将其恢复为原始状态(这可以是逻辑“ 1”传输,停止位传输或传输线的空闲状态)。
  • 成功完成接收的条件(起始和停止位的正确值)是完成信号。 从中(当由rdclk信号计时时)会生成就绪脉冲信号,该信号指示rxdata总线上存在有效数据。

我马上要指出,我不想从clk时钟准备好读取信号(出乎意料的,对吗?),以免将后续数据处理的速度与UART交换速率联系在一起。 发送器模块中也有类似的实现( 请参见下文 )。 测试接收器和发射器模块是基于Intel的IP Core FIFO进行的,并能够模拟用户和数据生成器的不同速度。 唯一的限制是数据产生者和使用者的时钟频率不得低于时钟频率clk。

接收器模块(Verilog)
// //   UART // //  rxdata ,  ready==1  error==0. //  ready   1       rdclk. // // : //    rx         // 2- .    ,     . //      8     - ( 9 ). // 2  -    ,      // . // //       . // . '0'    , . '1'     // idle     (. '1') // start-     (. '0') // stop-     (. '1') module uart_rx( nreset, //   (,   0) clk, //   UART, ..        UART rx, //   UART rdclk, //     (rxdata, ready) rxdata, //  ,    ready==1 ready, //    rxdata (  1) error, //    (  1) busy, //    ( ,   1) idle); //     (  1) input wire nreset; //   (,   0) input wire clk; //  , ..        UART input wire rx; //   UART input wire rdclk; //     output wire[7:0] rxdata; output wire ready; output error; output busy; output idle; //    ,   rdclk reg[2:0] done = 3'b000; //     ,  rdclk //assign ready = (done == 2'b10) ? 1'b1 : 1'b0; assign ready = (done[1] && !done[0]) ? 1'b1 : 1'b0; //     reg error = 1'b0; //          //         error   //   rx,       . wire fastsync = (error && rx); //     reg idle = 1'b1; //  : // d[9] -  , .. == 1 // d[8:1] -  // d[0] -  , .. == 0 reg[9:0] d = 10'b1xxxxxxxx1; //  .     2'b10 wire[1:0] status = { d[9], d[0] }; //   . wire complete = (status == 2'b10) ? 1'b1 : 1'b0; //    assign rxdata = d[8:1]; //    reg busy = 0; //       rx reg[1:0] cnt; always @(posedge clk, negedge nreset) begin if(!nreset) begin rxreset(); end else begin if(fastsync) begin rxreset(); end else begin if(busy == 1'b1) begin //   -,    rx if(cnt == 2'd0) begin //    //          // (..       ) d <= { rx, d[9:1] }; if(d[1] == 1'b0) begin //         ,   busy <= 1'b0; //     error <= (rx == 1'b1) ? 1'b0 : 1'b1; end else begin //      if(rx && (d == 10'b1111111111)) begin //      busy <= 1'b0; //    error <= 1'b1; end else begin //    // -    -      cnt <= 2'd3; end end end else begin //  -     cnt <= cnt - 2'd1; end end else begin //       if(!error) begin //   ,       if(rx == 1'b0) begin //            -   busy <= 1'b1; //    .     1, ..  //    d[0]==0 d <= 10'b1111111111; //   rx   1/2   // 1-  -    // 2-  -    (cnt  0) cnt <= 2'd0; // ..    ,     idle <= 1'b0; end else begin //    idle <= 1'b1; end end end end end end task rxreset; begin //    error <= 1'b0; //     (!?) idle <= 1'b1; //     busy <= 0; //     -,       complete d <= 10'b1xxxxxxxx1; end endtask always @(negedge rdclk, negedge nreset) begin if(!nreset) begin done <= 3'b000; end else begin //       complete. //     ready     //   complete  0  1    rdclk. done <= { complete, done[2:1] }; end end endmodule 


由于RX输入信号是异步的(可能是不稳定的),因此大多数元件都连接在主模块中接收器模块的前面。 该元素也是用Verilog编写的,但此处的代码没有意义。 相反,它是合成元素的美丽图画。

多数元素的综合方案
多数元素

发送器单元甚至更简单,我希望不需要其他注释。

变送器模块(始终在内部进行Verilog阻止和非阻止分配)
 // //   UART // // : // clk -     4    ,    // rdclk -   txdata, write, fetch.  ..  clk // txdata -   ,   write/fetch // write -      (1=) // fetch -      (1=) // tx -   UART // idle -    (1=,  ) // //  FIFO    dcfifo_component.lpm_showahead = "ON" module uart_tx( nreset, //   (,   0) clk, //   UART, ..        UART rdclk, //       txdata, //       write, //      (  1) idle, //     (  1) fetch, //     ,  rdclk tx); //   UART input wire nreset; //   (,   0) input wire clk; //  UART input wire rdclk; input wire[7:0] txdata; input wire write; output wire idle; output fetch; output tx; //    reg tx = 1'b1; reg fetch = 1'b0; //    4 reg[1:0] div4 = 2'd0; //  : reg[3:0] s = 4'd10; //    assign idle = (s == 4'd10); //    reg[7:0] d; //        reg sendstart; //        reg canfetch; //     ,  clk reg gotdata = 1'b0; //   clock domains reg[2:0] sync = 3'b000; //   rdclk  write reg wr = 1'b0; //    getdata==1       //      nextdata    //  gotdata==1.  ,      //  . //  gotdata     getdata. always @(posedge rdclk, negedge nreset) begin if(!nreset) begin wr <= 1'b0; sync <= 3'b000; //      fetch <= 1'b0; end else begin //   write wr <= write; //        sync <= { gotdata, sync[2:1] }; //     gotdata  //     .   //   . fetch <= (sync[1] && !sync[0]) ? 1'b1 : 1'b0; end end always @(posedge clk, negedge nreset) begin if(!nreset) begin //      div4 <= 2'd0; s <= 4'd10; gotdata <= 1'b0; end else begin //          sendstart = 1'b0; //        canfetch = wr; if(div4 == 2'd0) begin case(s) 4'd0: begin //       sendstart = 1'b1; //  ,     canfetch = 1'b0; end 4'd9: begin //    tx <= 1'b1; end 4'd10: begin //  idle,    end default: begin //    ,     tx <= d[0]; //     d <= { 1'b0, d[7:1] }; //  ,     canfetch = 1'b0; end endcase end else begin //    div4 <= div4 - 2'd1; if(s < 4'd9) begin //     9    ! canfetch = 1'b0; end end if(canfetch) begin //   ,     d <= txdata; //      gotdata <= 1'b1; if(idle /*s == 4'd10*/) begin //  idle -      sendstart = 1'b1; end else begin //         s <= 4'd0; end end if(gotdata) begin //    ,    gotdata <= 1'b0; end if(sendstart) begin //        tx <= 1'b0; //     s <= 4'd1; //    div4 <= 2'd3; end else begin if(div4 == 2'd0) begin if(s < 4'd10) begin //      s <= s + 4'd1; //    div4 <= 2'd3; end end end end end endmodule 


上面的发射机实现引起评论中的激烈讨论。 结果,尽管每个人似乎都同意这样做是可行的,但要谨慎。 为了您的安心,本模块已考虑所有上述指南进行了重写。 在我看来,从人类对所实现算法的看法来看,它并不比前一个复杂。

发射器模块(Verilog,思想上正确)
 // //   UART // // : // clk -     4    ,    // rdclk -   txdata, write, fetch.  ..  clk // txdata -   ,   write/fetch // write -      (1=) // fetch -      (1=) // tx -   UART // idle -    (1=,  ) // //  FIFO    dcfifo_component.lpm_showahead = "ON" module uart_tx( nreset, //   (,   0) clk, //   UART, ..        UART rdclk, //       txdata, //       write, //      (  1) idle, //     (  1) fetch, //     ,  rdclk tx); //   UART input wire nreset; //   (,   0) input wire clk; //  UART input wire rdclk; input wire[7:0] txdata; input wire write; output wire idle; output fetch; output tx; //    reg tx = 1'b1; reg fetch = 1'b0; //    4 reg[1:0] div4 = 2'd0; //  : reg[3:0] s = 4'd10; //    assign idle = (s == 4'd10); //    reg[7:0] d; //        reg sendstart; //        reg canfetch; //     ,  clk reg gotdata = 1'b0; //   clock domains reg[2:0] sync = 3'b000; //   rdclk  write reg wr = 1'b0; //    getdata==1       //      nextdata    //  gotdata==1.  ,      //  . //  gotdata     getdata. always @(posedge rdclk, negedge nreset) begin if(!nreset) begin wr <= 1'b0; sync <= 3'b000; //      fetch <= 1'b0; end else begin //   write wr <= write; //        sync <= { gotdata, sync[2:1] }; //     gotdata  //     .   //   . fetch <= (sync[1] && !sync[0]) ? 1'b1 : 1'b0; end end //   (?)      always //      sendstart  canfetch always @(*) begin //          sendstart = 1'b0; if(nreset) begin //        canfetch = wr; if(div4 == 2'd0) begin case(s) 4'd0: begin //       sendstart = 1'b1; //  ,     canfetch = 1'b0; end 4'd9: begin //    end 4'd10: begin //  idle,    end default: begin //     //  ,     canfetch = 1'b0; end endcase end else begin if(s < 4'd9) begin //     9    ! canfetch = 1'b0; end end if(canfetch && idle) begin //  idle -      sendstart = 1'b1; end end else begin //    reset      canfetch = 1'b0; end end always @(posedge clk, negedge nreset) begin if(!nreset) begin //      div4 <= 2'd0; s <= 4'd10; gotdata <= 1'b0; end else begin if(div4 == 2'd0) begin case(s) 4'd0: begin //    sendstart       end 4'd9: begin //    tx <= 1'b1; end 4'd10: begin //  idle,    end default: begin //    ,     tx <= d[0]; //     d <= { 1'b0, d[7:1] }; end endcase end else begin //    div4 <= div4 - 2'd1; end if(canfetch) begin //   ,     d <= txdata; //      gotdata <= 1'b1; if(!idle /*s == 4'd10*/) begin //         s <= 4'd0; end end else begin //     ,    gotdata <= 1'b0; end if(sendstart) begin //        tx <= 1'b0; //     s <= 4'd1; //    div4 <= 2'd3; end else begin if((div4 == 2'd0) && (s < 4'd10)) begin //      s <= s + 4'd1; //    div4 <= 2'd3; end end end end endmodule 



为了测试接收器和发射器,主模块写在膝盖上。 我要求您不要发誓,我知道设计错误(外部异步信号nreset,缺少FIFO复位等)。 但是出于验证功能的目的,它们并不重要。

我的演示板由50Mhz信号源提供时钟。 因此,在主模块中,我使用了PLL,在其输出C0处形成了一个与UART一起工作的频率(1.8432Mhz,实际上是1.843198Mhz),并且为了娱乐起见,我形成了300Mhz的频率(输出c1 PLL)来为信息处理电路的仿真计时。

主模块(Verilog)
 // // ..      UART    UART, //       FPGA,    //    FIFO IP CORE  DCFIFO. // //NB! //    SDC-    ! //     (    if , //   ). module uart( input wire clk50mhz, //   50Mhz input wire nreset, //    input wire rx, //   UART output wire tx, //   UART output wire overflow ); //   1.8432Mhz ( 1.843198Mhz) wire clk_1843200; //   1.2288Mhz ( 1.228799Mhz) //wire clk_1228800; //    300Mhz,  PLL wire clk300mhz; //     UART uart_pll pll50mhz(.inclk0(clk50mhz), .c0(clk_1843200) /*, .c1(clk_1228800)*/, .c1(clk300mhz)); //  UART 38400 //  (1843200/38400)/4 = 12 ('b1100). //  UART 57600 //  (1843200/57600)/4 = 8 //  UART 115200 //  (1843200/115200)/4 = 4 //  UART 230400 //  (1843200/230400)/4 = 2 //  UART 460800 //  (1843200/460800)/4 = 1 (..    !) //    UART wire uart_baud4; //     //   .data    1   .  //   uart_baud4    .clock/ //     uart_baud4     .clock uart_osc uart_osc_1(.clock(clk_1843200), .data(5'd2/*5'd4*//*5'd12*/-5'd1), .sload(uart_baud4), .cout(uart_baud4)); //wire uart_baud4 = clk_1843200; //      wire rxf; //       mfilter mfilter_rx(.clk(clk50mhz /*clk_1843200*/), .in(rx), .out(rxf)); //wire rxf = rx; //    wire[7:0] rxdata; wire rxready; wire error; uart_rx uart_rx_1(.nreset(nreset), .clk(uart_baud4), .rx(rxf), .rdclk(clk300mhz /*clk50mhz*/ /*clk_1843200*/), .rxdata(rxdata), .ready(rxready), .error(error)); wire[7:0] txdata; // ,   ,   wire txnone; // ,       wire fetch; wire full; //    //    uart_baud4 //    clk50mhz uart_fifo_rx uart_fifo_rx_1(.data(rxdata), .rdclk(clk300mhz /*clk50mhz*/ /*clk_1843200*/ /*uart_baud4*/), .rdreq(fetch), .wrclk(clk300mhz /*clk50mhz*/ /*clk_1843200*/ /*uart_baud4*/), .wrreq(rxready), .rdempty(txnone), .q(txdata), .wrfull(full)); assign overflow = ~error; uart_tx uart_tx_1(.nreset(nreset), .clk(uart_baud4), .rdclk(clk300mhz /*clk50mhz*/ /*clk_1843200*/), .txdata(txdata), .write(~txnone), .fetch(fetch), .tx(tx)); endmodule 


为了进行测试,使用了Zelax的testcom流量生成器。 不幸的是,我的USB / UART适配器拒绝以230400BPS以上的速度工作,因此所有测试均以此速度进行。

使用多数元素过滤输入信号RX的测试结果
RX预过滤器测试
信号分接头信号状态
无错误的UART接收器信号

此处来自入口的多数元素已被删除。
但是,当检查快速同步方案时,还能怎样模拟任意错误呢?
测试时无需预过滤RX信号
信号分接头信号状态
错误检测后快速接收器同步期间的信号

注意事项


抱歉,我没有上Quartus课程,也没有人问问题。 我偶然发现的内容以及对其他启动FPGA的警告:请确保在项目中创建一个SDC文件并描述其中的时钟频率。 是的,没有该项目的项目正在构建,但是如果合成器无法确定时钟的时序特性,可能会出现警告。 起初我一直不理会它们,直到杀死半天才确定执行代码时为什么在接收器模块中出现问题

 if(rx == 1'b0) begin busy <= 1'b1; d <= 10'b1111111111; cnt <= 2'd0; idle <= 1'b0; end else begin 

忙/闲信号已正确设置,但寄存器d的内容有时未更改。

附录:项目的SDC文件
 set_time_format -unit ns -decimal_places 3 #   50Mhz, (50/50 duty cycle) create_clock -name {clk50mhz} -period 20.000 -waveform { 0.000 10.000 } ############################################################################## Now that we have created the custom clocks which will be base clocks,# derive_pll_clock is used to calculate all remaining clocks for PLLs derive_pll_clocks -create_base_clocks derive_clock_uncertainty #   PLL    ? # altpll_component.clk0_divide_by = 15625, # altpll_component.clk0_duty_cycle = 50, # altpll_component.clk0_multiply_by = 576, # altpll_component.clk0_phase_shift = "0", #create_generated_clock -name clk_1843200 -source [get_ports {clk50mhz}] -divide_by 15625 -multiply_by 576 -duty_cycle 50 -phase 0 -offset 0 #  baudrate=38400 #     1/4 , .. duty=(1/4)*100=25% #create_generated_clock -name uart_baud4 -source [get_nets {pll50mhz|altpll_component|auto_generated|wire_pll1_clk[0]}] -divide_by 12 -duty_cycle 25 [get_nets {uart_osc_1|LPM_COUNTER_component|auto_generated|counter_reg_bit[0]}] #  baudrate=230400 #     1/4 , .. duty=(1/4)*100=50% create_generated_clock -name uart_baud4 -source [get_nets {pll50mhz|altpll_component|auto_generated|wire_pll1_clk[0]}] -divide_by 2 -duty_cycle 25 [get_nets {uart_osc_1|LPM_COUNTER_component|auto_generated|counter_reg_bit[0]}] #  baudrate=460800 #   1,    PLL,      . 


非常感谢所有对此文章发表评论的人! 其中,我收集了很多有用的信息,尽管有时会有些冲突。 在我看来,它们的价值远远大于上述算法的实现。 而且,毫无疑问,它们对于那些也敢于进入FPGA世界的人很有用。

外部链接列表

  1. 通用异步收发器(维基百科)
  2. 多数元素(维基百科)

Source: https://habr.com/ru/post/zh-CN427011/


All Articles