Pekerjaan praktis dengan FPGA di set Redd. Menguasai DMA untuk bus Avalon-ST dan beralih di antara bus Avalon-MM

Kami terus bergerak ke arah pembuatan perangkat nyata berdasarkan pada kompleks Redd FPGA. Untuk proyek All-Hardware lain, saya memerlukan penganalisa logika sederhana, jadi kita akan bergerak ke arah ini. Beruntung - dan sampai ke penganalisa bus USB (tapi ini masih dalam jangka panjang). Inti dari setiap penganalisa adalah RAM dan unit yang pertama mengunggah data ke dalamnya dan kemudian mengambilnya. Hari ini kita akan mendesainnya.

Untuk melakukan ini, kita akan menguasai blok DMA. Secara umum, DMA adalah topik favorit saya. Saya bahkan membuat artikel yang bagus tentang DMA pada beberapa pengontrol ARM . Dari artikel itu jelas bahwa DMA mengambil siklus jam dari bus. Pada artikel saat ini, kami akan mempertimbangkan bagaimana keadaan dengan sistem prosesor berbasis FPGA.




Penciptaan Perangkat Keras


Kami mulai membuat perangkat keras. Untuk memahami seberapa banyak blok DMA bertentangan dengan siklus clock, kita perlu melakukan pengukuran yang akurat pada beban tinggi pada bus Avalon-MM (Avalon Memory-Mapped). Kami telah menemukan bahwa jembatan Altera JTAG-ke-Avalon-MM tidak dapat menyediakan muatan bus yang tinggi. Karena itu, hari ini kita harus menambahkan inti prosesor ke sistem sehingga mengakses bus dengan kecepatan tinggi. Bagaimana ini dilakukan telah dijelaskan di sini . Demi optimalitas, mari kita menonaktifkan kedua cache untuk inti prosesor, tetapi buat satu bus yang sangat terhubung, seperti yang kami lakukan di sini .



Tambahkan 8 kilobyte memori program dan memori data. Ingat bahwa memori harus berupa dual-port dan memiliki alamat dalam kisaran khusus (untuk mencegahnya melompat, menguncinya, kami membahas alasannya di sini ).



Kami telah membuat proyek ribuan kali, jadi tidak ada yang sangat menarik dalam proses pembuatan itu sendiri (jika ada, semua langkah untuk membuatnya dijelaskan di sini ).

Basis sudah siap. Sekarang kita membutuhkan sumber data yang akan kita simpan dalam memori. Hal yang ideal adalah timer yang terus berdetak. Jika selama beberapa ukuran blok DMA tidak dapat memproses data, maka kita akan segera melihat ini dengan nilai yang hilang. Yaitu, jika di memori ada nilai 1234 dan 1236, itu berarti bahwa pada jam, ketika timer mengeluarkan 1235, blok DMA tidak mentransfer data. Buat fileTimer_ST.sv dengan penghitung sederhana:

module Timer_ST (
  input              clk,
  input              reset,
	
  input  logic       source_ready,
  output logic       source_valid,
  output logic[31:0] source_data
	
);
    logic [31:0] counter;
    always @ (posedge clk, posedge reset)
    if (reset == 1)
    begin
        counter <= 0;
    end else
    begin
        counter <= counter + 1;
    end

    assign source_valid = 1;
    assign source_data [31:24] = counter [7:0];
    assign source_data [23:16] = counter [15:8];
    assign source_data [15:8] = counter [23:16];
    assign source_data [7:0] = counter [31:24];

endmodule

Penghitung ini seperti pelopor: selalu siap (pada output source_valid selalu satu) dan selalu diperhitungkan (kecuali untuk saat-saat keadaan reset). Mengapa modul memiliki sinyal-sinyal ini - kami bahas dalam artikel ini .

Sekarang kita membuat komponen kita sendiri (bagaimana ini dilakukan dijelaskan di sini ). Automation keliru memilih bus Avalon_MM untuk kita. Ganti dengan avalon_streaming_source dan petakan sinyal seperti yang ditunjukkan di bawah ini:



Hebat. Tambahkan komponen kami ke sistem. Sekarang kami sedang mencari blok DMA ... Dan kami menemukan bukan hanya satu, tetapi tiga. Semuanya dijelaskan dalam dokumen Embedded Peripheral IP User Guide dari Altera (seperti biasa, saya memberi nama, tetapi bukan tautan, karena tautan selalu berubah).



Yang mana yang akan digunakan? Saya tidak bisa menahan nostalgia. Kembali pada tahun 2012, saya membuat sistem berdasarkan bus PCIe. Semua manual dari Altera berisi contoh berdasarkan yang pertama dari blok ini. Tapi dia dengan komponen PCIe memberi kecepatan tidak lebih dari 4 megabyte per detik. Pada hari-hari itu, saya meludah dan menulis blok DMA saya. Sekarang saya tidak ingat kecepatannya, tetapi ia mengemudikan data dari drive SATA ke batas kemampuan drive dan SSD saat itu. Artinya, saya telah menajamkan gigi di blok ini. Tapi saya tidak akan memasukkan perbandingan tiga blok. Faktanya adalah bahwa hari ini kita harus bekerja dengan sumber berdasarkan Avalon-ST (Avalon Streaming Interface), dan hanya blok Modular Scatter-Gather DMA yang mendukung sumber tersebut . Di sini kita letakkan di diagram.

Dalam pengaturan blok, pilih modeStreaming ke Memori Dipetakan . Plus - Saya ingin mengarahkan data dari peluncuran ke pengisian SDRAM, jadi saya mengganti unit transfer data maksimum dari 1 kilobyte menjadi 4 megabita. Benar, saya diperingatkan bahwa pada akhirnya, parameter FMax tidak akan terlalu panas (bahkan jika Anda mengganti blok maksimum dengan 2 kilobyte). Tetapi untuk hari ini, FMax dapat diterima (104 MHz), dan kemudian kita akan mengetahuinya. Saya membiarkan parameter yang tersisa tidak berubah. Anda juga dapat mengatur mode transmisi ke Full Word Access Only, ini akan meningkatkan FMax menjadi 109 MHz. Tapi kami tidak akan memperjuangkan kinerja hari ini.



Begitu. Sumbernya adalah, DMA. Penerima ... SDRAM? Dalam kondisi pertempuran di masa depan, ya. Tetapi hari ini kita membutuhkan ingatan dengan karakteristik yang diketahui. Sayangnya, SDRAM perlu mengirim perintah secara berkala yang mengambil beberapa siklus jam, ditambah memori ini dapat ditempati oleh regenerasi. Oleh karena itu, alih-alih, sekarang kita akan menggunakan memori FPGA bawaan. Semuanya bekerja untuknya dalam satu langkah, tanpa penundaan yang tidak terduga.

Karena pengontrol SDRAM adalah port tunggal, memori internal juga dapat digunakan secara eksklusif dalam mode port tunggal. Itu penting. Faktanya adalah bahwa kita ingin menulis ke memori menggunakan master blok DMA, tetapi di sisi lain, kita ingin membaca dari memori ini menggunakan inti prosesor atau blok Altera JTAG-ke-Avalon-MM. Tangan mengulurkan tangan dan menghubungkan blok tulis dan baca ke dua port berbeda ... Tapi Anda tidak bisa! Sebaliknya, itu dilarang oleh kondisi masalah. Karena hari ini adalah mungkin, tetapi besok kami akan mengganti memori dengan port tunggal secara eksklusif. Secara umum, kami mendapatkan blok tiga komponen seperti itu (timer, DMA dan memori):



Yah, dan murni untuk pro forma, saya akan menambahkan UT dan sysid ke sistem JTAG (meskipun yang kedua tidak membantu, saya masih harus menyulap dengan adaptor JTAG pula). Apa itu, dan bagaimana penambahan mereka memecahkan masalah kecil, kita sudah pelajari. Saya tidak akan mewarnai ban, semuanya jelas dengan mereka. Cukup perlihatkan bagaimana tampilannya di proyek saya:



Itu saja. Sistem sudah siap. Kami menetapkan alamat, menetapkan vektor prosesor, menghasilkan sistem (jangan lupa bahwa Anda harus menyimpan dengan nama yang sama dengan proyek itu sendiri, maka itu akan pergi ke tingkat atas hierarki), tambahkan ke proyek. Jadikan reset leg virtual, sambungkan clk ke pin_25 leg. Kami sedang merakit proyek, menuangkannya ke dalam peralatan ... Bagaimana, hal yang buruk, di kantor kosong karena lokasi total terpencil? ... Itu kesepian dan menakutkan baginya, mungkin, sendirian ... Tapi aku terganggu.

Membuat bagian perangkat lunak


Latihan


Di Editor BSP dengan gerakan tangan yang biasa saya aktifkan dukungan C ++. Saya begitu sering memasukkan tangkapan layar dari kasus ini sehingga saya berhenti melakukannya. Tapi tangkapan layar lain, meskipun sudah terlihat, masih sangat umum. Jadi mari kita bahas sekali lagi. Kami ingat bahwa sistem mencoba untuk memasukkan data ke dalam bagian memori terbesar. Dan begitulah Buffer . Karena itu, kami memaksakan segalanya untuk Data :



Eksperimen program


Kami membuat kode yang hanya mengisi memori dengan isi sumber (dalam peran penghitung).

Lihat kode
#include "sys/alt_stdio.h"
#include <altera_msgdma.h>
#include <altera_msgdma_descriptor_regs.h>
#include <system.h>
#include <string.h>

int main()
{ 
  alt_putstr("Hello from Nios II!\n");

  memset (BUFFER_BASE,0,BUFFER_SIZE_VALUE);

  //  ,   
  IOWR_ALTERA_MSGDMA_CSR_CONTROL(MSGDMA_0_CSR_BASE,
      ALTERA_MSGDMA_CSR_STOP_DESCRIPTORS_MASK);

  //   ,      ,
  //    ,   .  .

  //    FIFO
  IOWR_ALTERA_MSGDMA_DESCRIPTOR_READ_ADDRESS(MSGDMA_0_DESCRIPTOR_SLAVE_BASE,
      (alt_u32)0);
  IOWR_ALTERA_MSGDMA_DESCRIPTOR_WRITE_ADDRESS(MSGDMA_0_DESCRIPTOR_SLAVE_BASE,
      (alt_u32)BUFFER_BASE);
  IOWR_ALTERA_MSGDMA_DESCRIPTOR_LENGTH(MSGDMA_0_DESCRIPTOR_SLAVE_BASE,
      BUFFER_SIZE_VALUE);
  IOWR_ALTERA_MSGDMA_DESCRIPTOR_CONTROL_STANDARD(MSGDMA_0_DESCRIPTOR_SLAVE_BASE,
      ALTERA_MSGDMA_DESCRIPTOR_CONTROL_GO_MASK);

   //  ,    
   IOWR_ALTERA_MSGDMA_CSR_CONTROL(MSGDMA_0_CSR_BASE,
       ALTERA_MSGDMA_CSR_STOP_ON_ERROR_MASK
       & (~ALTERA_MSGDMA_CSR_STOP_DESCRIPTORS_MASK)
       &(~ALTERA_MSGDMA_CSR_GLOBAL_INTERRUPT_MASK)) ;


   //   
   static const alt_u32 errMask = ALTERA_MSGDMA_CSR_STOPPED_ON_ERROR_MASK |
           ALTERA_MSGDMA_CSR_STOPPED_ON_EARLY_TERMINATION_MASK |
           ALTERA_MSGDMA_CSR_STOP_STATE_MASK |
           ALTERA_MSGDMA_CSR_RESET_STATE_MASK;

  volatile alt_u32 status;
  do
  {
     status = IORD_ALTERA_MSGDMA_CSR_STATUS(MSGDMA_0_CSR_BASE);
  } while (!(status & errMask) &&(status & ALTERA_MSGDMA_CSR_BUSY_MASK));     

  alt_putstr("You can play with memory!\n");

  /* Event loop never exits. */
  while (1);

  return 0;
}


Kami mulai, kami menunggu pesan "Anda dapat bermain dengan memori!" , letakkan program pada jeda dan lihat memori, mulai dari alamat 0. Awalnya saya sangat takut:



Dari alamat 0x80, penghitung mengubah nilainya dengan tajam. Apalagi jumlahnya sangat besar. Tapi ternyata semuanya baik-baik saja. Di tempat kami, penghitung tidak pernah berhenti dan selalu siap, dan DMA memiliki antrian baca-depan sendiri. Biarkan saya mengingatkan Anda pengaturan blok DMA:



0x80 byte adalah 0x20 kata tiga puluh dua bit. Hanya 32 desimal. Semuanya cocok bersama. Dalam kondisi debugging, ini tidak menakutkan. Dalam kondisi pertempuran, sumber akan bekerja lebih benar (kesiapannya akan diatur ulang). Karena itu, kami mengabaikan bagian ini. Di daerah lain, meteran dihitung secara berurutan. Saya hanya akan menampilkan fragmen dump dengan lebar. Ambil satu kata yang saya periksa secara keseluruhan.



Tidak mempercayai mata saya, saya menulis kode yang secara otomatis memeriksa data:

  volatile alt_u32* pData = (alt_u32*)BUFFER_BASE;
  volatile alt_u32 cur = pData[0x10];
  int nLine = 0;
  for (volatile int i=0x11;i<BUFFER_SIZE_VALUE/4;i++)
  {
	  if (pData[i]!=cur+1)
	  {
		  alt_printf("Problem at 0x%x\n",i*4);
		  if (nLine++ > 10)
		  {
			  break;
		  }
	  }
	  cur = pData[i];
  }

Dia juga tidak mengungkapkan masalah apa pun.

Berusaha menemukan setidaknya beberapa masalah


Padahal, tidak adanya masalah tidak selalu baik. Sebagai bagian dari artikel, saya perlu menemukan masalahnya, dan kemudian menunjukkan bagaimana mereka diperbaiki. Bagaimanapun, masalahnya jelas. Bus yang sibuk tidak dapat meneruskan data tanpa penundaan! Harus ada penundaan! Tapi mari kita periksa mengapa semuanya terjadi begitu indah. Pertama-tama, mungkin ternyata semuanya ada di FIFO dari blok DMA. Kurangi ukurannya sampai minimum:



Semuanya terus bekerja! Baik. Pastikan bahwa kami memprovokasi jumlah akses ke bus lebih dari dimensi FIFO. Tambahkan hit counter:



Teks yang sama:
  volatile alt_u32 status;
  volatile int n = 0;
  do
  {
	  status = IORD_ALTERA_MSGDMA_CSR_STATUS(MSGDMA_0_CSR_BASE);
	  n += 1;
  } while (!(status & errMask) &&(status & ALTERA_MSGDMA_CSR_BUSY_MASK));  


Pada akhir pekerjaan, ini adalah 29. Ini lebih dari 16. Artinya, FIFO akan meluap. Untuk jaga-jaga, mari kita tambahkan lebih banyak bacaan register status. Tidak membantu.

Dengan kesedihan, saya terputus dari kompleks Redd jarak jauh, memulangkan proyek ke papan tempat memotong roti saya yang ada, yang dapat saya hubungkan dengan osiloskop sekarang (di kantor, tidak ada yang berada di kejauhan, saya tidak dapat mencapai osiloskop). Menambahkan dua port ke timer:

   output    clk_copy,
   output    ready_copy

Dan mengangkat mereka:

    assign clk_copy = clk;
    assign ready_copy = source_ready;

Akibatnya, modul mulai terlihat seperti ini:

module Timer_ST (
   input           clk,
   input           reset,
	
   input logic     source_ready,
   output logic    source_valid,
   output logic[31:0] source_data,

   output    clk_copy,
   output    ready_copy
	
);
    logic [31:0] counter;
    always @ (posedge clk, posedge reset)
    if (reset == 1)
    begin
        counter <= 0;
    end else
    begin
        counter <= counter + 1;
    end

    assign source_valid = 1;
    assign source_data [31:24] = counter [7:0];
    assign source_data [23:16] = counter [15:8];
    assign source_data [15:8] = counter [23:16];
    assign source_data [7:0] = counter [31:24];

    assign clk_copy = clk;
    assign ready_copy = source_ready;

endmodule

Di rumah saya punya model yang lebih kecil dengan kristal, jadi saya harus mengurangi selera memori. Dan ternyata program primitif saya tidak akan muat menjadi bagian 4 kilobyte. Jadi topik yang diangkat dalam artikel terakhir adalah, oh, seberapa relevan. Memori dalam sistem - hampir tidak cukup!

Ketika program dimulai, kami mendapatkan lonjakan siap dari 16 atau 17 langkah. Ini diisi dengan FIFO dari blok DMA. Efek yang sama yang membuatku takut pada awalnya. Data inilah yang akan membentuk pengisian buffer yang sangat salah.



Selanjutnya, kita memiliki gambar yang indah pada 40960 nanodetik, yaitu, 2048 siklus (dengan kristal rumah, buffer harus dikurangi menjadi 8 kilobyte, yaitu, 2048 kata tiga puluh dua bit). Inilah permulaannya:



Inilah akhirnya:



Nah, dan seluruh - tidak ada satu kegagalan. Tidak, sudah jelas bahwa ini akan terjadi, tetapi ada beberapa harapan ...

Mungkin kita harus mencoba menulis di bus, dan tidak hanya membacanya? Saya menambahkan blok GPIO ke sistem:



Menambahkan entri ke dalamnya sambil menunggu kesiapan:



Teks yang sama
  volatile alt_u32 status;
  volatile int n = 0;
  do
  {
	status = IORD_ALTERA_MSGDMA_CSR_STATUS(MSGDMA_0_CSR_BASE);
       IOWR_ALTERA_AVALON_PIO_DATA (PIO_0_BASE,0x01);
       IOWR_ALTERA_AVALON_PIO_DATA (PIO_0_BASE,0x00);

       n += 1;
  } while (!(status & errMask) &&(status & ALTERA_MSGDMA_CSR_BUSY_MASK));  


Tidak ada masalah dan hanya itu! Siapa yang harus disalahkan?

Tidak ada mukjizat, tetapi ada hal-hal yang belum dijelajahi


Siapa yang harus disalahkan? Dengan kesedihan, saya mulai mempelajari semua menu alat Platform Designer dan, sepertinya, menemukan petunjuk. Seperti apa bentuk ban itu? Set kabel yang terhubung dengan klien. Begitu? Sepertinya begitu. Kami melihat ini dari angka-angka di editor. Hanya, tujuan kedua artikel itu adalah untuk menunjukkan bagaimana bus dapat dibagi menjadi dua segmen independen, yang masing-masing bekerja tanpa mengganggu yang lain.

Tapi mari kita lihat pesan yang muncul saat sistem dibuat. Sorot kata kunci:



Dan ada banyak pesan serupa: ditambahkan, ditambahkan. Ternyata setelah mengedit dengan pena, banyak hal tambahan ditambahkan ke sistem. Bagaimana Anda melihat skema di mana semua ini sudah tersedia? Saya masih berenang dalam hal ini, tetapi kita bisa mendapatkan jawaban yang paling mungkin dalam kerangka artikel dengan memilih item menu ini: Gambar yang



dibuka sangat mengesankan dengan sendirinya, tetapi saya tidak akan memberikannya. Dan segera



saya akan memilih tab ini: Dan di sana kita melihat hal berikut: Saya akan



menunjukkan yang lebih besar paling penting:



Ban tidak digabungkan! Mereka tersegmentasi! Saya tidak dapat membenarkannya (mungkin para ahli akan mengoreksi saya dalam komentar), tetapi tampaknya sistem memasukkan sakelar untuk kami! Sakelar inilah yang menciptakan segmen bus yang terisolasi, dan sistem utama dapat bekerja secara paralel dengan unit DMA, yang saat ini dapat mengakses memori tanpa konflik!

Kami memprovokasi masalah nyata


Setelah menerima semua pengetahuan ini, kami menyimpulkan bahwa kami dapat memprovokasi masalah dengan sangat baik. Ini diperlukan untuk memastikan bahwa sistem pengujian dapat membuatnya, yang berarti bahwa lingkungan pengembangan benar-benar menyelesaikannya secara mandiri. Kami tidak akan merujuk ke perangkat abstrak di bus, tetapi ke memori Buffer yang sama sehingga blok cmd_mux_005 mendistribusikan bus antara inti prosesor dan blok DMA. Kami menulis ulang fungsi tunggu yang lama menderita seperti ini:



Teks yang sama
  volatile alt_u32 status;
  volatile int n = 0;
  volatile alt_u32* pBuf = (alt_u32*)BUFFER_BASE;
  volatile alt_u32 sum = 0;
  do
  {
	  status = IORD_ALTERA_MSGDMA_CSR_STATUS(MSGDMA_0_CSR_BASE);
	  sum += pBuf[n];

	  n += 1;
  } while (!(status & errMask) &&(status & ALTERA_MSGDMA_CSR_BUSY_MASK));


Dan akhirnya, dips muncul di gelombang!



Fungsi pemeriksaan memori juga menemukan banyak penghilangan:



Ya, dan kami melihat dengan sempurna bahwa data digeser dari baris ke baris:



Dan di sini adalah contoh tempat buruk tertentu (6CCE488F hilang):



Sekarang kita melihat bahwa percobaan dilakukan dengan benar, hanya lingkungan pengembangan yang dilakukan optimasi untuk kita. Ini adalah kasus ketika saya mengucapkan frasa โ€œSemua dengan cerdas melukai semua bajaโ€ tidak dengan ejekan, tetapi dengan rasa terima kasih. Terima kasih kepada pengembang Quartus untuk masalah ini!

Kesimpulan


Kami belajar cara memasukkan blok DMA ke sistem untuk mentransfer data streaming ke memori. Kami juga memastikan bahwa proses mengunduh perangkat lain di bus tidak akan mengganggu proses pengunduhan. Lingkungan pengembangan akan secara otomatis membuat segmen terisolasi yang akan berjalan secara paralel dengan bagian lain dari bus. Tentu saja, jika seseorang beralih ke segmen yang sama, konflik dan waktu yang dihabiskan untuk menyelesaikannya tidak terhindarkan, tetapi programmer dapat meramalkan hal-hal seperti itu.

Pada artikel selanjutnya, kita akan mengganti RAM dengan pengontrol SDRAM, dan pengatur waktu dengan "kepala" nyata dan membuat penganalisis logis pertama. Apakah ini akan berfungsi? Saya belum tahu. Saya harap masalah tidak muncul.

All Articles