"Hello World!" pada FPGA atau versi UART berikutnya

Halo Dunia saya!  pada FPGA atau versi UART berikutnya

Akhirnya, tangan saya mencapai studi tentang FPGA. Dan entah bagaimana ternyata ternyata salah: saya menulis driver untuk perangkat keras untuk Linux, saya memprogram mikrokontroler, saya membaca sirkuit (dan mendesain sedikit), saya perlu tumbuh lebih jauh.

Karena menurut saya tidak menarik untuk berkedip LED, saya memutuskan untuk melakukan hal yang sederhana. Yaitu, tulis modul penerima dan pemancar untuk UART, gabungkan di dalam FPGA (pada saat yang sama pahami cara menggunakan IP Core), baik, dan uji semuanya pada perangkat keras nyata.

Saya katakan segera bahwa itu bukan tugas untuk membuat inti parameterized universal dari tugas. Ini hanyalah proyek uji coba, dengan subjek "rasakan apa itu FPGA dan bagaimana berkomunikasi dengannya."

Jadi, mari kita mulai dengan penerima. Algoritma ini dijelaskan dengan cukup baik , jadi saya akan ulangi di sini hanya poin utamanya.

  • Frekuensi pengambilan sampel sinyal RX empat kali lebih besar dari laju transmisi UART yang diperlukan.
  • Kondisi untuk dimulainya penerimaan dianggap sebagai transisi dari sinyal input RX dari tingkat tinggi ke yang rendah, jika penerimaan saat ini tidak sedang berlangsung.
  • Kondisi untuk identifikasi awal bit yang dapat dipercaya dianggap sebagai retensi sinyal RX dalam keadaan rendah pada jam kedua dari frekuensi sampling. Pada saat yang sama, kita praktis jatuh ke tengah-tengah pulsa bit, yang akan memungkinkan kita untuk mengambil sampel pulsa lebih lanjut setiap 4 siklus.
  • Jika terjadi kesalahan pada bit mulai atau berhenti, atur kesalahan sinyal kesalahan. Berdasarkan itu, kami membentuk sinyal fastsync, yang akan kami gunakan di masa depan untuk sinkronisasi penerima cepat.
  • Setelah mengidentifikasi bit mulai, kami memulai penerimaan bit data berurutan, dimulai dengan yang termuda. Data yang diterima ditulis ke dalam register dengan pergeseran ke kanan sedikit demi sedikit. Kondisi untuk akhir penerimaan akan menjadi deteksi bit awal pada posisi ke-0 register geser.
  • Sinkronisasi cepat pada penerima terdiri dari membawanya ke keadaan semula setelah mendeteksi kesalahan ketika sinyal RX naik ke level tinggi (ini bisa berupa transmisi logis “1”, transmisi bit stop atau keadaan idle dari saluran transmisi).
  • Kondisi untuk penyelesaian penerimaan yang sukses (nilai bit start dan stop yang benar) adalah sinyal lengkap. Dari itu (ketika clocked oleh sinyal rdclk), sinyal pulsa siap dihasilkan, yang menunjukkan adanya data yang valid pada bus rxdata.

Saya akan segera mencatat bahwa saya tidak ingin clock sinyal baca siap dari sinyal clock clk (tiba-tiba, kan?), Agar tidak mengikat kecepatan pemrosesan data selanjutnya ke nilai tukar UART. Implementasi serupa ada di modul pemancar ( lihat di bawah ). Kumpulan uji modul penerima dan pemancar didasarkan pada Intel Core IP FIFO, dan dengan kemampuan untuk mensimulasikan kecepatan yang berbeda untuk konsumen dan generator data. Satu-satunya batasan adalah bahwa frekuensi jam dari produsen dan konsumen data tidak boleh lebih rendah dari frekuensi clock clk.

Modul Penerima (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 


Karena sinyal input RX tidak sinkron dan (mungkin) tidak stabil, elemen mayoritas terhubung di depan modul penerima di modul utama . Elemen ini juga ditulis dalam Verilog, tetapi kodenya tidak masuk akal di sini. Sebagai gantinya, gambar yang indah dari elemen yang disintesis.

Skema disintesis dari elemen mayoritas
Elemen yang dibahas

Unit pemancar bahkan lebih sederhana dan, saya harap, tidak memerlukan komentar tambahan.

Modul pemancar (selalu ada tugas pemblokiran Verilog dan non-pemblokiran)
 // //   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 


Implementasi pemancar di atas menyebabkan diskusi panas di komentar. Meskipun, sebagai hasilnya, semua orang tampaknya setuju bahwa itu mungkin dilakukan, tetapi dengan hati-hati. Untuk ketenangan pikiran Anda sendiri, modul telah ditulis ulang dengan mempertimbangkan semua pedoman yang disebutkan. Menurut pendapat saya, tidak jauh lebih rumit dari yang sebelumnya dari sudut pandang persepsi manusia tentang algoritma yang diterapkan.

Modul Transmitter (Verilog, benar secara ideologis)
 // //   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 



Untuk menguji penerima dan pemancar, modul utama ditulis pada lutut. Saya meminta Anda untuk tidak bersumpah, saya tahu kesalahan desain (nreset sinyal asinkron eksternal, kurangnya FIFO reset, dll). Tetapi untuk tujuan memverifikasi fungsionalitas, mereka tidak signifikan.

Papan demo saya clock dari sumber sinyal 50Mhz. Oleh karena itu, dalam modul utama, saya menggunakan PLL, pada output C0 di mana saya membentuk frekuensi untuk bekerja dengan UART (1.8432Mhz, sebenarnya 1.843198Mhz) dan, untuk bersenang-senang, saya membentuk frekuensi 300Mhz (output c1 PLL) untuk mencatat simulasi rangkaian pemrosesan informasi.

Modul utama (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 


Untuk pengujian, generator traffic testcom dari Zelax digunakan. Sayangnya, adaptor USB / UART saya menolak untuk bekerja dengan kecepatan di atas 230400BPS, jadi semua pengujian dilakukan pada kecepatan ini.

Hasil pengujian dengan memfilter sinyal input RX menggunakan elemen mayoritas
Pengujian Pra-Filter RX
Sinyal Ketuk Status Sinyal
Sinyal penerima UART tanpa ada kesalahan

Dan di sini elemen mayoritas dari pintu masuk telah dihapus.
Tapi apa, bagaimana lagi saya bisa mensimulasikan kesalahan sewenang-wenang ketika memeriksa skema sinkronisasi cepat?
Menguji tanpa menyaring sinyal RX
Sinyal Ketuk Status Sinyal
Sinyal selama menerima penerima cepat setelah deteksi kesalahan

Catatan


Maaf, saya tidak mengikuti kursus Quartus dan tidak ada yang mengajukan pertanyaan. Apa yang saya temukan pada diri saya dan apa yang saya peringatkan tentang FPGA start-up lainnya: Pastikan untuk membuat file SDC dalam proyek dan menggambarkan frekuensi jam di dalamnya. Ya, proyek sedang dibangun tanpanya, meskipun peringatan mungkin muncul jika synthesizer tidak dapat menentukan karakteristik waktu jam. Pada awalnya saya mengabaikan mereka sampai saya membunuh setengah hari untuk menentukan masalah mengapa di modul penerima saya ketika mengeksekusi kode

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

sinyal sibuk dan idle diatur dengan benar, tetapi isi register d terkadang tidak berubah.

Lampiran: File SDC untuk proyek
 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,      . | altpll_component | auto_generated | wire_pll1_clk [ 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,      . | counter_reg_bit [ 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,      . 


Banyak terima kasih manusia kepada semua orang yang menulis komentar pada artikel! Dari jumlah tersebut, saya mengumpulkan banyak informasi yang berguna, walaupun terkadang agak saling bertentangan. Menurut pendapat saya, nilainya jauh lebih besar daripada implementasi algoritma yang dijelaskan di atas. Dan, tidak diragukan lagi, mereka akan bermanfaat bagi mereka yang juga berani naik ke dunia FPGA.

Daftar tautan eksternal

  1. Universal Asynchronous Transceiver (Wikipedia)
  2. Elemen Mayoritas (Wikipedia)

Source: https://habr.com/ru/post/id427011/


All Articles