Pendahuluan
Apakah Anda ingat permainan ular sejak kecil, di mana seekor ular berlari di layar mencoba makan apel? Artikel ini menjelaskan implementasi game kami pada FPGA 1 .

Gambar 1. Gameplay
Pertama, mari kita perkenalkan diri kita sendiri dan jelaskan alasan mengapa kita mengerjakan proyek ini. Ada 3 dari kita: Tymur Lysenko , Daniil Manakovskiy dan Sergey Makarov . Sebagai mahasiswa tahun pertama dari Innopolis University , kami memiliki kursus "Arsitektur Komputer", yang diajarkan secara profesional dan memungkinkan pelajar untuk memahami struktur komputer tingkat rendah. Pada titik tertentu selama kursus, instruktur memberi kami kesempatan untuk mengembangkan proyek untuk FPGA untuk poin tambahan dalam kursus. Motivasi kami bukan hanya tingkat, tetapi minat kami untuk mendapatkan lebih banyak pengalaman dalam desain perangkat keras, berbagi hasil, dan akhirnya, memiliki permainan yang menyenangkan.
Sekarang, mari kita masuk ke detail gelap yang dalam.
Tinjauan proyek
Untuk proyek kami, kami memilih game yang mudah diimplementasikan dan menyenangkan, yaitu "Snake". Struktur implementasinya berjalan sebagai berikut: pertama, input diambil dari joystick SPI, kemudian diproses, dan akhirnya, gambar dihasilkan ke monitor VGA dan skor ditampilkan pada layar 7-segmen (dalam hex). Meskipun logika permainannya intuitif dan mudah, VGA dan joystick merupakan tantangan yang menarik dan implementasinya telah menghasilkan pengalaman bermain game yang bagus.
Gim ini memiliki aturan berikut. Seorang pemain mulai dengan kepala ular tunggal. Tujuannya adalah memakan apel, yang dihasilkan secara acak di layar setelah yang sebelumnya dimakan. Selanjutnya, ular itu diperpanjang 1 ekor setelah memuaskan rasa lapar. Ekor bergerak satu demi satu, mengikuti kepala. Ular itu selalu bergerak. Jika batas layar tercapai, ular dipindahkan ke sisi lain layar. Jika kepala menyentuh ekor, permainan berakhir.
- Altera Cyclone IV (EP4CE6E22C8N) dengan 6272 elemen logis, onboard 50 MHz clock, VGA warna 3-bit, tampilan 7-segmen 8 digit. FPGA tidak dapat mengambil input analog ke pinnya.
- SPI Joystick (KY-023)
- Monitor VGA yang mendukung kecepatan refresh 60 Hz
- Quartus Prime Lite Edition 18.0.0 Build 614
- Verilog HDL IEEE 1364-2001
- Papan tempat memotong roti
- Elemen listrik:
- 8 konektor pria-wanita
- 1 konektor perempuan-perempuan
- 1 konektor pria-pria
- 4 resistor (4,7 KΩ)
Tinjauan arsitektur
Arsitektur proyek merupakan faktor penting untuk dipertimbangkan. Gambar 2 menunjukkan arsitektur ini dari sudut pandang tingkat atas:

Gambar 2. Tampilan desain tingkat atas ( pdf )
Seperti yang Anda lihat, ada banyak input, output, dan beberapa modul. Bagian ini akan menjelaskan arti setiap elemen dan menentukan pin mana yang digunakan pada papan untuk port.
Input utama
Input utama yang diperlukan untuk implementasi adalah res_x_one , res_x_two , res_y_one , res_y_two , yang digunakan untuk menerima arah joystick saat ini. Gambar 3 menunjukkan pemetaan antara nilai-nilai mereka dan arah.
Masukan | Kiri | Benar | Naik | Turun | Tidak ada perubahan arah |
---|
res_x_one (PIN_30) | 1 | 0 | x | x | 1 |
res_x_two (PIN_52) | 1 | 0 | x | x | 0 |
res_y_one (PIN_39) | x | x | 1 | 0 | 1 |
res_y_two (PIN_44) | x | x | 1 | 0 | 0 |
Gambar 3. Pemetaan input dan arah joystick
- clk - jam papan (PIN_23)
- setel ulang - sinyal untuk menyetel ulang game dan berhenti mencetak (PIN_58)
- warna - ketika 1, semua warna yang mungkin dihasilkan ke layar dan hanya digunakan untuk tujuan demonstrasi (PIN_68)
Modul utama
joystick_input digunakan untuk menghasilkan kode arah berdasarkan input dari joystick.
game_logic
game_logic berisi semua logika yang diperlukan untuk memainkan game. Modul ini menggerakkan ular ke arah tertentu. Selain itu, ini bertanggung jawab untuk makan apel dan deteksi tabrakan. Selain itu, ia menerima koordinat x dan y saat ini dari piksel pada layar dan mengembalikan entitas yang ditempatkan pada posisi.
VGA_Draw
Laci menetapkan warna piksel ke nilai tertentu berdasarkan posisi saat ini ( iVGA_X, iVGA_Y ) dan entitas saat ini ( ent ).
VGA_Ctrl
Menghasilkan bitstream kontrol ke output VGA ( V_Sync, H_Sync, R, G, B ).
SSEG_Display 2
SSEG_Display adalah driver untuk menampilkan skor saat ini pada tampilan 7-segmen.
Vga_clk
VGA_clk menerima clock 50MHz dan memotongnya menjadi 25.175 MHz.
game_upd_clk
game_upd_clk adalah modul yang menghasilkan jam khusus yang memicu pembaruan keadaan permainan.
Keluaran
- VGA_B - pin biru VGA (PIN_144)
- VGA_G - pin hijau VGA (PIN_1)
- VGA_R - pin merah VGA (PIN_2)
- VGA_HS - Sinkronisasi horizontal VGA (PIN_142)
- VGA_VS - Sinkronisasi vertikal VGA (PIN_143)
- sseg_a_to_dp - menentukan yang mana dari 8 segmen yang menyala (PIN_115, PIN_119, PIN_120, PIN_121, PIN_124, PIN_125, PIN_126, PIN_126, PIN_127)
- sseg_an - menentukan tampilan dari 4 7 segmen mana yang akan digunakan (PIN_128, PIN_129, PIN_132, PIN_133)
Implementasi

Gambar 4. SPI Joystick (KY-023)
Saat menerapkan modul input, kami menemukan bahwa tongkat menghasilkan sinyal analog. Joystick memiliki 3 posisi untuk setiap sumbu:
- top - ~ output 5V
- pertengahan - output 2.5V
- rendah - ~ keluaran 0V
Inputnya sangat mirip dengan sistem ternary: untuk sumbu-X, kita memiliki true
(kiri), false
(kanan) dan state yang undetermined
, di mana joystick tidak di kiri atau di kanan. Masalahnya adalah bahwa papan FPGA hanya dapat memproses input digital. Karenanya kita tidak dapat mengubah logika terner ini menjadi biner hanya dengan menulis beberapa kode. Solusi pertama yang disarankan adalah menemukan konverter Analog-Digital, tetapi kemudian kami memutuskan untuk menggunakan pengetahuan sekolah kami tentang fisika dan menerapkan pembagi tegangan 3 . Untuk mendefinisikan tiga status, kita membutuhkan dua bit: 00 false
, 01 tidak undefined
dan 11 true
. Setelah beberapa pengukuran, kami menemukan bahwa di papan kami, batas antara nol dan satu adalah sekitar 1,7V. Jadi, kami membangun skema berikut (gambar dibuat menggunakan circuitlab 4 ):

Gambar 5. Sirkuit untuk ADC untuk joystick
Implementasi fisik dibangun menggunakan item kit Arduino, dan terlihat sebagai berikut:

Gambar 6. Implementasi ADC
Rangkaian kami mengambil satu input untuk setiap sumbu dan menghasilkan dua output: yang pertama datang langsung dari tongkat dan menjadi nol hanya jika joystick menghasilkan zero
. Yang kedua adalah 0 pada kondisi yang undetermined
, tetapi masih 1 pada true
. Ini adalah hasil tepat yang kami harapkan.
Logika modul input adalah:
- Kami menerjemahkan logika ternary kami ke kabel biner sederhana untuk setiap arah;
- Pada setiap siklus jam, kami memeriksa apakah hanya satu arah yang
true
(ular tidak dapat bergerak secara diagonal); - Kami membandingkan arah baru kami dengan yang sebelumnya untuk mencegah ular makan sendiri dengan tidak membiarkan pemain mengubah arah menjadi sebaliknya.
Bagian dari kode modul input reg left, right, up, down; initial begin direction = `TOP_DIR; end always @(posedge clk) begin //1 left = two_resistors_x; right = ~one_resistor_x; up = two_resistors_y; down = ~one_resistor_y; if (left + right + up + down == 3'b001) //2 begin if (left && (direction != `RIGHT_DIR)) //3 begin direction = `LEFT_DIR; end //same code for other directions end end
Output ke VGA
Kami memutuskan untuk membuat output dengan resolusi 640x480 pada layar 60Hz yang berjalan pada 60 FPS.
Modul VGA terdiri dari 2 bagian utama: driver dan laci . Driver menghasilkan bitstream yang terdiri dari sinyal sinkronisasi vertikal, horizontal, dan warna yang diberikan ke output VGA. Artikel 5 yang ditulis oleh @SlavikMIPT menjelaskan prinsip-prinsip dasar bekerja dengan VGA. Kami telah mengadaptasi driver dari artikel ke board kami.
Kami memutuskan untuk memecah layar menjadi kisi elemen 40x30, yang terdiri dari kuadrat 16x16 piksel. Setiap elemen mewakili 1 entitas permainan: baik apel, kepala ular, ekor atau tidak sama sekali.
Langkah selanjutnya dalam implementasi kami adalah membuat sprite untuk entitas.
Siklon IV hanya memiliki 3 bit untuk mewakili warna pada VGA (1 untuk Merah, 1 untuk Hijau, dan 1 untuk Biru). Karena keterbatasan seperti itu, kami perlu menerapkan konverter agar sesuai dengan warna gambar ke yang tersedia. Untuk tujuan itu, kami membuat skrip python yang membagi nilai RGB dari setiap piksel dengan 128.
Skrip python from PIL import Image, ImageDraw filename = "snake_head" index = 1 im = Image.open(filename + ".png") n = Image.new('RGB', (16, 16)) d = ImageDraw.Draw(n) pix = im.load() size = im.size data = [] code = "sp[" + str(index) + "][{i}][{j}] = 3'b{RGB};\\\n" with open("code_" + filename + ".txt", 'w') as f: for i in range(size[0]): tmp = [] for j in range(size[1]): clr = im.getpixel((i, j)) vg = "{0}{1}{2}".format(int(clr[0] / 128),
Asli | Setelah skrip |

| 
|
Gambar 7. Perbandingan antara input dan output
Tujuan utama laci adalah untuk mengirim warna piksel ke VGA berdasarkan posisi saat ini ( iVGA_X, iVGA_Y ) dan entitas saat ini ( ent ). Semua sprite dikode keras tetapi dapat dengan mudah diubah dengan membuat kode baru menggunakan skrip di atas.
Logika laci always @(posedge iVGA_CLK or posedge reset) begin if(reset) begin oRed <= 0; oGreen <= 0; oBlue <= 0; end else begin // DRAW CURRENT STATE if (ent == `ENT_NOTHING) begin oRed <= 1; oGreen <= 1; oBlue <= 1; end else begin // Drawing a particular pixel from sprite oRed <= sp[ent][iVGA_X % `H_SQUARE][iVGA_Y % `V_SQUARE][0]; oGreen <= sp[ent][iVGA_X % `H_SQUARE][iVGA_Y % `V_SQUARE][1]; oBlue <= sp[ent][iVGA_X % `H_SQUARE][iVGA_Y % `V_SQUARE][2]; end end end
Output ke tampilan 7-segmen
Untuk memungkinkan pemain melihat skor mereka, kami memutuskan untuk menampilkan skor game ke tampilan 7-segmen. Karena kekurangan waktu, kami menggunakan kode dari EP4CE6 Starter Board Documentation 2 . Modul ini menampilkan angka heksadesimal ke layar.
Logika game
Selama pengembangan, kami mencoba beberapa pendekatan, namun, kami berakhir dengan pendekatan yang membutuhkan jumlah memori minimal, mudah diimplementasikan dalam perangkat keras, dan dapat mengambil manfaat dari komputasi paralel.
Modul ini menjalankan beberapa fungsi. Ketika VGA menggambar piksel pada setiap siklus jam mulai dari kiri atas bergerak ke kanan bawah, modul VGA_Draw, yang bertanggung jawab untuk menghasilkan warna untuk piksel, perlu mengidentifikasi warna yang digunakan untuk koordinat saat ini. Itulah yang harus dihasilkan modul logika permainan - kode entitas untuk koordinat yang diberikan.
Selain itu, ia harus memperbarui status permainan hanya setelah layar penuh ditarik. Sinyal yang dihasilkan oleh modul game_upd_clk digunakan untuk menentukan kapan harus memperbarui.
Status permainan
Status permainan terdiri dari:
- Koordinat kepala ular
- Susunan koordinat ekor ular. Array dibatasi oleh 128 elemen dalam implementasi kami
- Jumlah ekor
- Koordinat apel
- Game over flag
- Game memenangkan bendera
Pembaruan status permainan mencakup beberapa tahap:
- Gerakkan kepala ular ke koordinat baru, berdasarkan arah yang diberikan. Jika ternyata koordinat berada di tepinya dan perlu diubah lebih lanjut, maka kepala harus melompat ke tepi layar yang lain. Misalnya, arah ditetapkan ke kiri, dan koordinat X saat ini adalah 0. Oleh karena itu, koordinat X baru harus menjadi sama dengan alamat horizontal terakhir.
- Koordinat baru dari kepala ular diuji terhadap koordinat apel:
2.1. Jika mereka sama dan array tidak penuh, tambahkan ekor baru ke array dan tambahkan counter ekor. Ketika penghitung mencapai nilai tertinggi (128 dalam kasus kami), permainan memenangkan bendera sedang disiapkan dan itu berarti, ular itu tidak dapat tumbuh lagi, dan permainan masih berlanjut. Ekor baru ditempatkan pada koordinat sebelumnya dari kepala ular. Koordinat acak untuk X dan Y harus diambil untuk menempatkan apel di sana.
2.2. Dalam hal mereka tidak sama, bertukar koordinat secara berurutan dari ekor yang berdekatan. (n +1) - ekor ke-9 harus menerima koordinat ke-n, jika ekor ke-n ditambahkan sebelum (n +1)-ke-ke-3. Ekor pertama menerima koordinat lama kepala. - Periksa, apakah koordinat baru kepala ular bertepatan dengan koordinat ekor mana pun. Jika itu masalahnya, game over flag dinaikkan dan game berhenti.
Pembuatan koordinat acak
Angka acak dihasilkan dengan mengambil bit acak yang dihasilkan oleh register shift bergilir linier umpan balik 6-bit (LFSR) 6 . Untuk memasukkan angka-angka ke dalam layar, mereka dibagi dengan dimensi kotak permainan dan sisanya diambil.
Kesimpulan
Setelah 8 minggu bekerja, proyek ini berhasil dilaksanakan. Kami telah memiliki pengalaman dalam pengembangan game dan berakhir dengan versi menyenangkan dari game "Snake" untuk FPGA. Gim ini dapat dimainkan, dan keterampilan kami dalam pemrograman, merancang arsitektur, dan soft-skill telah meningkat.
Segmen yang diakui
Kami ingin menyampaikan terima kasih dan terima kasih khusus kami kepada para profesor kami Muhammad Fahim dan Alexander Tormasov yang telah memberi kami pengetahuan mendalam dan kesempatan untuk mempraktikkannya. Kami dengan tulus berterima kasih kepada Vladislav Ostankovich karena telah menyediakan perangkat keras penting yang digunakan dalam proyek ini dan Temur Kholmatov untuk membantu debugging. Kami tidak akan lupa untuk mengingat Anastassiya Boiko menggambar sprite indah untuk permainan. Kami juga ingin menyampaikan pendapat kami yang tulus kepada Rabab Marouf untuk mengoreksi dan mengedit artikel ini.
Terima kasih untuk semua yang telah membantu kami menguji permainan dan mencoba membuat rekor. Semoga Anda menikmati bermain itu!
Referensi
[1]: Proyek di Github
[2]: [FPGA] EP4CE6 Dokumentasi Papan Pemula
[3]: Pembagi tegangan
[4]: Alat untuk memodelkan sirkuit
[5]: Adaptor VGA untuk FPGA Altera Cyclone III
[6]: Register pergeseran linier-umpan balik (LFSR) di Wikipedia
LFSR dalam Kode FPGA - VHDL & Verilog
Tekstur apel
Ide untuk menghasilkan angka acak
Palnitkar, S. (2003). Verilog HDL: Panduan untuk Desain dan Sintesis Digital, Edisi Kedua.