
Jadi, dalam
artikel pertama dari siklus dikatakan bahwa yang terbaik adalah menggunakan sistem prosesor untuk mengontrol peralatan kami diimplementasikan menggunakan FPGA untuk kompleks Redd, setelah itu ditunjukkan pada artikel pertama dan kedua bagaimana membuat sistem ini. Ya, sudah selesai, kita bahkan dapat memilih beberapa kernel yang sudah jadi dari daftar untuk memasukkannya ke dalamnya, tetapi tujuan utamanya adalah mengelola kernel kustom kita sendiri. Waktunya telah tiba untuk mempertimbangkan bagaimana memasukkan kernel yang sewenang-wenang dalam sistem prosesor.
Semua artikel siklus:
Pengembangan "firmware" paling sederhana untuk FPGA yang dipasang di Redd, dan debugging menggunakan tes memori sebagai contohPengembangan "firmware" paling sederhana untuk FPGA yang dipasang di Redd. Bagian 2. Kode programUntuk memahami teori hari ini, Anda harus mencari dan mengunduh dokumen
Spesifikasi Antarmuka Avalon , karena bus
Avalon adalah bus dasar untuk sistem NIOS II. Saya akan merujuk pada bagian, tabel dan gambar untuk revisi dokumen 26 September 2018.
Kami membuka bagian 3 yang ditujukan untuk Memory Mapped Interfaces, atau lebih tepatnya - 3.2. Tabel 9 mencantumkan sinyal bus. Harap dicatat bahwa semua sinyal ini adalah opsional. Saya tidak menemukan sinyal tunggal yang memiliki "Ya" di kolom Diperlukan. Kami mungkin tidak meneruskan sinyal ini atau itu ke perangkat kami. Oleh karena itu, dalam kasus yang paling sederhana, bus sangat sederhana untuk diimplementasikan. Awal tabel terlihat seperti ini:

Seperti yang Anda lihat, semua sinyal dijelaskan dengan sangat baik (kecuali bahwa ini dilakukan dalam bahasa Inggris). Di bawah ini adalah bagan waktu untuk berbagai kasus. Kasus yang paling sederhana tidak menimbulkan pertanyaan. Sekarang saya akan mengambil grafik waktu dari dokumen dan mencakup beberapa baris dengan isian transparan (semuanya opsional, kami berhak mengecualikan salah satu pertimbangan).

Menakutkan Tapi semuanya sederhana: kita diberi alamat dan strobo
baca , kita harus mengatur data di bus readdata. Dan sebaliknya: kita diberi alamat, data pada bus writedata dan strobo tulis, dan kita harus mengambil data. Sama sekali tidak menakutkan, bus sinkron yang khas.
Baris byteenable rahasia diperlukan untuk kasus ketika akses memori bukan kata-kata 32-bit. Ini sangat penting ketika kita mendesain kernel universal. Tetapi ketika kita merancang inti satu hari, kita cukup menulis dalam dokumen tentang inti ini (saya adalah lawan dari tanda di kepala saya, tetapi seseorang dapat membatasi itu untuk ini) bahwa kita perlu menggunakan kata-kata 32-bit dan hanya itu. Nah, dan sinyal
respon , itu sangat istimewa, dan itu tidak menarik bagi kita pada prinsipnya.
Terkadang penting bahwa ketika peralatan tidak siap, adalah mungkin untuk menunda pengoperasian bus selama beberapa siklus jam. Dalam hal ini, sinyal
WaitRequest harus ditambahkan. Bagan waktu akan berubah sebagai berikut:

Sementara
WaitRequest dikokang, penyihir tahu bahwa perangkat kami sedang sibuk. Hati-hati jika sinyal ini tidak diatur ulang, seluruh sistem akan "membeku" saat ditangani, jadi hanya reboot dari FPGA yang dapat mengatur ulang. JTAG hang dengan sistem. Terakhir kali saya mengamati fenomena ini adalah dalam persiapan artikel ini, jadi ingatannya masih hidup.
Lebih lanjut dalam dokumen perusahaan, kasus-kasus yang lebih produktif tentang perpipaan data dan transaksi batch dipertimbangkan, tetapi tugas artikel ini bukan untuk mempertimbangkan semua opsi yang mungkin, tetapi untuk menunjukkan kepada pembaca cara untuk bekerja, menekankan bahwa semua ini sama sekali tidak menakutkan, jadi kami akan membatasi diri kami pada dua opsi sederhana ini.
Mari kita merancang beberapa perangkat sederhana yang secara berkala tidak tersedia di bus. Hal pertama yang terlintas dalam pikiran adalah antarmuka serial. Saat transmisi sedang berlangsung, kami akan membuat sistem menunggu. Dan dalam kehidupan, saya sangat menyarankan untuk tidak melakukan ini: prosesor akan berhenti sampai akhir transaksi yang sibuk, tetapi ini adalah kasus yang ideal untuk sebuah artikel, karena kode implementasi akan dapat dimengerti dan tidak terlalu rumit. Secara umum, kami akan membuat pemancar serial yang dapat mengirim data dan sinyal pemilihan chip ke dua perangkat.

Mari kita mulai dengan opsi ban paling sederhana. Mari kita buat port keluaran paralel, yang membentuk sinyal pilihan kristal.

Untuk ini, saya akan mengambil proyek yang diperoleh di artikel sebelumnya, tetapi untuk menghindari kebingungan, saya akan meletakkannya di direktori AVALON_DEMO. Saya tidak akan mengubah nama file lain. Di direktori ini, buat direktori
my_cores . Nama direktori bisa apa saja. Kami akan menyimpan inti kami di dalamnya. Benar, hari ini akan menjadi satu. Buat file
CrazySerial.sv dengan konten berikut:
module CrazySerial ( input clk, input reset, input [1:0] address, input write, input [31:0] writedata, output reg [1:0] cs ); always @(posedge clk, posedge reset) begin if (reset == 1) begin cs <= 0; end else begin if (write) case (address) 2'h00: cs <= writedata [1:0]; default:; endcase end end endmodule
Mari kita perbaiki. Pertama-tama, garis antarmuka.
CLK dan
reset adalah jam dan garis reset. Nama
alamat ,
baris tulis dan
writedata diambil dari tabel dengan daftar sinyal dari dokumen
Antarmuka Memori yang Dipetakan .


Bahkan, saya bisa memberi nama. Menghubungkan garis logis dengan yang fisik akan dilakukan nanti. Tetapi jika Anda memberi nama, seperti dalam tabel, lingkungan pengembangan akan menghubungkannya dengan sendirinya. Karena itu, lebih baik mengambil nama dari tabel.
Nah,
cs adalah garis pemilihan kristal yang akan keluar dari chip.
Implementasinya sendiri sepele. Saat diatur ulang, outputnya di-zeroed. Jadi - pada setiap pengukuran kita memeriksa apakah ada sinyal
tulis . Jika ada alamat yang sama dengan nol, maka klik datanya. Tentu saja, mungkin untuk menambahkan decoder di sini, yang akan mencegah pilihan dua perangkat sekaligus, tetapi apa yang baik dalam hidup akan membebani artikel. Artikel ini hanya menyediakan langkah-langkah yang paling penting, namun, perlu dicatat bahwa dalam hidup semuanya bisa dilakukan lebih rumit.
Bagus Kami siap untuk memperkenalkan kode ini ke dalam sistem prosesor. Kami pergi ke
Perancang Platform , pilih sebagai file input sistem yang kami buat dalam percobaan sebelumnya:

Kami menarik perhatian ke item
Komponen Baru di sudut kiri atas:

Untuk menambahkan komponen Anda, klik pada item ini. Dalam dialog yang terbuka, isi kolom. Dan untuk artikel ini, isilah hanya nama komponen:

Sekarang buka tab
File dan klik
Add File :

Tambahkan file yang dibuat sebelumnya, pilih dalam daftar dan klik
Analisis File Sintesis :

Tidak ada kesalahan dalam parsing
SystemVerilog , tetapi ada beberapa kesalahan konseptual. Mereka disebabkan oleh kenyataan bahwa beberapa jalur tidak terhubung dengan benar oleh lingkungan pengembangan. Kami pergi ke tab
Sinyal & Antarmuka dan perhatikan di sini:

Baris
cs tidak tepat ditugaskan ke antarmuka
avalon_slave0 , sinyal
readdata . Tapi kemudian semua baris lainnya dikenali dengan benar, berkat fakta bahwa kami memberi mereka nama dari tabel dokumen. Tapi apa yang harus dilakukan dengan garis masalah? Mereka harus ditugaskan ke antarmuka seperti
saluran . Untuk melakukan ini, klik pada item “tambahkan antarmuka”

Di menu tarik-turun, pilih
saluran :

Kami mendapatkan antarmuka baru:

Jika diinginkan, dapat diubah namanya. Benar, ini tentu akan diperlukan jika kita ingin membuat beberapa antarmuka eksternal. Sebagai bagian dari artikel, kami akan membiarkannya nama
conduit_end . Sekarang kita menghubungkan garis
cs dengan mouse dan menariknya ke antarmuka ini. Kita harus mengatur untuk melemparkan sinyal di bawah baris
conduit_end , maka kita akan diizinkan untuk melakukan ini. Di tempat lain, kursor akan muncul sebagai lingkaran dicoret. Pada akhirnya, kita harus memiliki ini:

Ganti tipe sinyal dengan
readdata dengan, katakanlah,
chipselect . Gambar akhir:

Namun kesalahan tetap ada.
Bus avalon tidak diberi sinyal reset. Kami memilih
avalon_slave_0 dari daftar dan melihat propertinya.

Ganti
tidak dengan
reset . Pada saat yang sama, kami akan memeriksa properti antarmuka lainnya.

Dapat dilihat bahwa berbicara dengan kata-kata. Nah, sejumlah hal lain dari dokumentasi tersebut dikonfigurasi di sini. Diagram waktu apa yang diperoleh dalam kasus ini akan diambil di bagian paling bawah dari properti:

Sebenarnya, tidak ada lagi kesalahan. Anda bisa mengklik
Selesai . Modul yang kami buat muncul di pohon perangkat:

Tambahkan ke sistem prosesor, sambungkan sinyal jam dan setel ulang. Kami menghubungkan bus data ke prosesor
Data Master . Klik dua kali
Conduit_end dan beri nama eksternal sinyal, katakanlah,
baris . Ternyata entah bagaimana seperti ini:

Penting untuk tidak lupa bahwa karena kami menambahkan blok ke sistem, kami harus memastikan bahwa itu tidak bertentangan dengan siapa pun di ruang alamat. Dalam kasus khusus ini, tidak ada konflik dalam gambar, tapi bagaimanapun, saya akan memilih item menu
System-> Assign Base Addresses .
Itu saja. Blok dibuat, dikonfigurasi, ditambahkan ke sistem. Klik tombol
Generate HDL , lalu
Finish .
Kami membuat konsep kasar proyek, setelah itu kami pergi ke
Perencana Pin dan menetapkan kaki. Ternyata seperti ini:

Yang sesuai dengan kontak B22 dan C22 dari konektor antarmuka.
Kami membuat perakitan akhir, memuat sistem prosesor ke dalam FPGA. Sekarang kita perlu memperbaiki kode program. Luncurkan Eclipse.
Biarkan saya mengingatkan Anda bahwa saya saat ini bekerja dengan proyek yang terletak di direktori yang berbeda relatif terhadap pekerjaan terakhir saya dengan Redd. Agar tidak bingung, saya akan menghapus proyek lama dari pohon (tetapi hanya dari pohon, tanpa menghapus file sendiri).

Selanjutnya, saya klik tombol kanan mouse pada pohon kosong dan pilih
Impor dari menu:

Berikutnya -
Umum-> Proyek yang Ada ke dalam Ruang Kerja :

Dan cukup pilih direktori di mana file proyek disimpan:


Kedua proyek yang diwarisi dari percobaan sebelumnya akan terhubung ke lingkungan pengembangan.

Saya akan menyoroti item berikutnya dalam sebuah bingkai:
Setiap kali setelah mengubah konfigurasi perangkat keras, pilih Nios II -> Hasilkan item menu BSP untuk proyek BSP lagi.

Sebenarnya, setelah operasi ini, sebuah blok baru muncul di file
\ AVALON_DEMO \ software \ SDRAMtest_bsp \ system.h :
/* * CrazySerial_0 configuration * */ #define ALT_MODULE_CLASS_CrazySerial_0 CrazySerial #define CRAZYSERIAL_0_BASE 0x4011020 #define CRAZYSERIAL_0_IRQ -1 #define CRAZYSERIAL_0_IRQ_INTERRUPT_CONTROLLER_ID -1 #define CRAZYSERIAL_0_NAME "/dev/CrazySerial_0" #define CRAZYSERIAL_0_SPAN 16 #define CRAZYSERIAL_0_TYPE "CrazySerial"
Pertama-tama, kami tertarik pada
CRAZYSERIAL_0_BASE yang konstan.
Tambahkan kode berikut ke fungsi
utama () :
while (true) { IOWR_ALTERA_AVALON_PIO_DATA (CRAZYSERIAL_0_BASE,0x00); IOWR_ALTERA_AVALON_PIO_DATA (CRAZYSERIAL_0_BASE,0x01); IOWR_ALTERA_AVALON_PIO_DATA (CRAZYSERIAL_0_BASE,0x02); IOWR_ALTERA_AVALON_PIO_DATA (CRAZYSERIAL_0_BASE,0x03); }
Kami mulai men-debug dan melihat isi baris dengan osiloskop. Harus ada kode biner tambahan. Dia disana.

Selain itu, frekuensi akses ke port sangat bagus:

Sekitar 25 MHz adalah setengah frekuensi bus (2 siklus clock). Terkadang waktu akses bukan 2 siklus, tetapi lebih lama. Ini karena pelaksanaan operasi percabangan dalam program. Secara umum, akses paling sederhana ke bus bekerja.
Saatnya untuk menambahkan fungsi port serial misalnya. Untuk melakukan ini, tambahkan sinyal antarmuka
waitrequest terkait dengan bus dan sepasang sinyal port serial -
sck dan
sdo . Total, kami mendapatkan fragmen kode berikut pada
systemverilog :

Teks yang sama: module CrazySerial ( input clk, input reset, input [1:0] address, input write, input [31:0] writedata, output waitrequest, output reg [1:0] cs, output reg sck, output sdo );
Menurut aturan bentuk yang baik, Anda perlu membuat mesin sederhana yang akan mengirimkan data. Sayangnya, mesin yang paling tidak rumit dalam artikel ini akan terlihat sangat sulit. Tetapi pada kenyataannya, jika saya tidak meningkatkan fungsionalitas mesin (dan sebagai bagian dari artikel saya tidak akan melakukan ini), maka ia hanya akan memiliki dua status: transmisi sedang berlangsung dan transmisi tidak sedang berlangsung. Karena itu, saya dapat menyandikan status dengan satu sinyal:
pengiriman reg = 0;
Selama transmisi, saya memerlukan penghitung bit, pembagi jam (saya melakukan perangkat yang sengaja lambat) dan register geser untuk data yang dikirimkan. Tambahkan register yang sesuai:
reg [2:0] bit_cnt = 0; reg [3:0] clk_div = 0; reg [7:0] shifter = 0;
Saya akan membagi frekuensi dengan 10 (dipandu oleh prinsip "mengapa tidak?"). Dengan demikian, pada langkah kelima saya akan memiringkan SCK, dan pada kesepuluh - jatuhkan baris ini, setelah itu - pergi ke bit data berikutnya. Pada semua langkah lain, cukup tambahkan penghitung pembagi. Penting untuk tidak lupa bahwa pada langkah keempat Anda juga perlu menambah penghitung, dan pada yang kesembilan - nol itu. Jika kita menghilangkan transisi ke bit berikutnya, maka logika yang ditentukan terlihat seperti ini:
if (sending) begin case (clk_div) 4: begin sck <= 1; clk_div <= clk_div + 1; end 9: begin sck <= 0; clk_div <= 0; // < > end default: clk_div <= clk_div + 1; endcase end else
Pergi ke bit berikutnya itu mudah. Mereka menggeser register geser, kemudian, jika bit saat ini adalah yang ketujuh, mereka berhenti bekerja dengan mengubah keadaan mesin, jika tidak mereka meningkatkan bit counter.
shifter <= {shifter[6:0],1'b0}; if (bit_cnt == 7) begin sending <= 0; end else begin bit_cnt <= bit_cnt + 1; end
Sebenarnya itu saja. Bit output selalu diambil dari bit tinggi register shift:
assign sdo = shifter [7];
Dan garis paling penting untuk revisi saat ini. Sinyal
waitrequest diiringi ke kesatuan selalu ketika data serial sedang dikirim. Yaitu, itu adalah salinan dari sinyal
pengiriman yang mengatur keadaan mesin:
assign waitrequest = sending;
Nah, dan ketika menulis ke alamat 1 (ingat, di sini kami memiliki pengalamatan dalam kata-kata 32-bit), kami memasukkan data ke dalam register geser, nol penghitung dan memulai proses transfer:
if (write) //... 2'h01: begin bit_cnt <= 0; clk_div <= 0; sending <= 1; shifter <= writedata [7:0]; end default:; endcase end
Sekarang saya akan memberikan semua fragmen yang dijelaskan sebagai satu teks: module CrazySerial ( input clk, input reset, input [1:0] address, input write, input [31:0] writedata, output waitrequest, output reg [1:0] cs, output reg sck, output sdo ); reg sending = 0; reg [2:0] bit_cnt = 0; reg [3:0] clk_div = 0; reg [7:0] shifter = 0; always @(posedge clk, posedge reset) begin if (reset == 1) begin cs <= 0; sck <= 0; sending <= 0; end else begin if (sending) begin case (clk_div) 4: begin sck <= 1; clk_div <= clk_div + 1; end 9: begin clk_div <= 0; shifter <= {shifter[6:0],1'b0}; sck <= 0; if (bit_cnt == 7) begin sending <= 0; end else begin bit_cnt <= bit_cnt + 1; end end default: clk_div <= clk_div + 1; endcase end else if (write) case (address) 2'h00: cs <= writedata [1:0]; 2'h01: begin bit_cnt <= 0; clk_div <= 0; sending <= 1; shifter <= writedata [7:0]; end default:; endcase end end assign sdo = shifter [7]; assign waitrequest = sending; endmodule
Kami mulai memperkenalkan kode baru ke dalam sistem. Sebenarnya, path sama dengan saat membuat komponen, tetapi beberapa langkah sudah bisa dihilangkan. Sekarang kita akan berkenalan dengan proses penyempurnaan. Pergi ke
Perancang Platform . Jika kita hanya mengubah kode Verilog, akan sangat sederhana untuk melakukan operasi
Hasilkan HDL untuk sistem yang sudah selesai. Tetapi karena modul memiliki garis-garis baru (yaitu, antarmuka telah berubah), itu perlu diulang. Untuk melakukan ini, pilih di pohon, tekan tombol kanan mouse dan pilih
Edit .

Kami sedang mengedit sistem yang ada. Jadi cukup buka tab
File dan klik
Analisis File Sinthesis :

Terjadi kesalahan yang dapat diprediksi. Tapi kita sudah tahu bahwa garis yang salah yang harus disalahkan. Oleh karena itu, kita pergi ke tab
Sinyal & Antarmuka , seret
sck dan
sdo di sepanjang baris yang sama dari antarmuka
avalon_slave_0 ke antarmuka
conduit_end :

Ganti nama bidang
Tipe Sinyal untuknya. Hasilnya harus sebagai berikut:

Sebenarnya itu saja. Klik
Selesai , panggil
Hasilkan File HDL untuk sistem prosesor, konsep proyek di Quartus, tetapkan kaki baru:

Ini adalah kontak A21 dan A22 dari konektor antarmuka, kami membuat perakitan akhir, mengisi "firmware" di FPGA.
Besi diperbarui. Sekarang programnya. Mari kita pergi ke Eclipse. Apa yang kita ingat lakukan di sana? Benar juga, jangan lupa pilih
Generate BSP .
Sebenarnya itu saja. Tetap menambahkan fungsionalitas ke program. Mari mentransfer sepasang byte ke port serial, tetapi kami akan mengirimkan byte pertama ke perangkat yang dipilih oleh baris
cs [0] , dan yang kedua -
cs [1] .
IOWR_ALTERA_AVALON_PIO_DATA (CRAZYSERIAL_0_BASE,0x01); IOWR_ALTERA_AVALON_PIO_DATA (CRAZYSERIAL_0_BASE+4,0x12); IOWR_ALTERA_AVALON_PIO_DATA (CRAZYSERIAL_0_BASE,0x02); IOWR_ALTERA_AVALON_PIO_DATA (CRAZYSERIAL_0_BASE+4,0x34); IOWR_ALTERA_AVALON_PIO_DATA (CRAZYSERIAL_0_BASE,0x00);
Harap dicatat bahwa tidak ada pemeriksaan ketersediaan di sana. Paket berjalan satu demi satu. Namun demikian, pada osiloskop semuanya ternyata cukup konsisten

Sinar kuning adalah
cs [0] ,
sinar hijau adalah
sdo ,
sinar violet adalah
sck , dan
sinar biru adalah
cs [1] . Dapat dilihat bahwa kode 0x12 pergi ke perangkat pertama, 0x34 ke perangkat kedua.
Membaca dilakukan dengan cara yang sama, tetapi saya tidak bisa memberikan contoh yang indah, kecuali untuk pembacaan dangkal dari isi kaki konektor. Tetapi contoh itu begitu merosot sehingga tidak menarik untuk dilakukan. Tetapi di sini perlu dicatat bahwa ketika membaca pengaturan bus ini bisa sangat penting:

Jika ada garis
Baca , maka bagan waktu baca akan muncul di dialog pengaturan. Dan itu akan menunjukkan pengaruh parameter ini. Saat membaca kaki konektor, itu masih tidak akan terlihat, tetapi ketika membaca dari FIFO atau RAM yang sama - sepenuhnya. RAM dapat dikonfigurasikan untuk mengeluarkan data segera setelah alamat dikirimkan, atau dapat dikeluarkan secara sinkron. Dalam kasus kedua, latensi ditambahkan. Lagi pula, bus mengatur alamat, mengatur strobo ... Tetapi tidak ada data di tepi terdekat dari sinyal jam. Mereka akan muncul setelah bagian depan ini ... Yaitu, sistem memiliki latensi satu latensi. Dan itu hanya perlu diperhitungkan dengan mengatur parameter ini. Singkatnya, jika Anda tidak membaca apa yang diharapkan, pertama-tama periksa apakah Anda perlu mengkonfigurasi latensi. Selebihnya - membaca tidak berbeda dengan menulis.
Baiklah, izinkan saya mengingatkan Anda sekali lagi bahwa lebih baik tidak menghapus kesiapan bus untuk operasi jangka panjang, jika tidak sangat mungkin untuk secara drastis mengurangi kinerja sistem. Sinyal siap baik untuk menahan transaksi selama beberapa siklus clock, dan tidak hingga 80 siklus clock, seperti dalam contoh saya. Tetapi pertama-tama, contoh lain akan merepotkan untuk artikel, dan kedua, untuk kernel satu hari, ini cukup dapat diterima. Anda akan sepenuhnya menyadari tindakan Anda dan akan menghindari situasi ketika bus diblokir. Benar, jika inti bertahan waktu yang dialokasikan untuk itu, asumsi seperti itu dapat merusak kehidupan di masa depan, ketika semua orang lupa tentang hal itu, dan itu memperlambat segalanya. Tapi nanti.
Namun demikian, kami telah belajar membuat inti prosesor mengendalikan inti kami. Semuanya jelas dengan dunia yang bisa dialamatkan, sekarang saatnya untuk berhadapan dengan dunia streaming. Tetapi kita akan melakukan ini di artikel berikutnya, dan mungkin bahkan beberapa artikel.
Kesimpulan
Artikel tersebut menunjukkan bagaimana kernel Verilog yang sewenang-wenang dapat dihubungkan untuk mengontrol sistem prosesor Nios II. Pilihan untuk koneksi paling sederhana ke bus Avalon, serta koneksi di mana bus bisa dalam keadaan sibuk, ditampilkan. Tautan ke literatur diberikan, dari mana Anda dapat mengetahui mode operasi lain dari bus Avalon dalam mode Memory Mapped.
Proyek yang dihasilkan dapat diunduh di
sini .