Sebagai kelanjutan dari
artikel pertama , saya ingin menunjukkan contoh bekerja dengan FPGA (FPGA) dengan python sebagai contoh. Artikel ini akan membahas aspek pengujian secara lebih rinci. Jika kerangka kerja
MyHDL memungkinkan orang yang bekerja pada python, menggunakan sintaks dan ekosistem yang sudah dikenal, untuk melihat ke dunia FPGA, maka pengembang FPGA yang berpengalaman tidak memahami arti menggunakan python. Paradigma deskripsi perangkat keras untuk MyHDL dan Verilog serupa, dan memilih bahasa tertentu adalah masalah kebiasaan dan selera. Verilog / VHDL singkatan dari fakta bahwa firmware telah ditulis dalam bahasa-bahasa ini untuk waktu yang lama, dan pada kenyataannya mereka adalah standar untuk deskripsi peralatan digital. Python, sebagai pemula dalam bidang ini, dapat bersaing dalam lingkungan pengujian penulisan. Sebagian besar waktu pengembang FPGA dihabiskan untuk menguji desain mereka. Selanjutnya, saya ingin menunjukkan dengan contoh bagaimana ini dilakukan dalam python dengan MyHDL.
Misalkan ada tugas untuk menggambarkan perangkat yang bekerja dengan memori pada FPGA. Untuk kesederhanaan, saya akan mengambil memori yang berkomunikasi dengan perangkat lain melalui antarmuka paralel (dan bukan melalui serial, misalnya, I2C). Lingkaran mikro semacam itu tidak selalu praktis mengingat kenyataan bahwa banyak pin diperlukan untuk bekerja dengannya, di sisi lain, pertukaran informasi yang lebih cepat dan mudah disediakan. Misalnya, 1645RU1U domestik dan analognya.

Deskripsi modul
Catatannya terlihat seperti ini: FPGA memberikan alamat sel 16-bit, data 8-bit, menghasilkan sinyal tulis WE (write enable). Karena OE (memungkinkan output) dan CE (memungkinkan chip) selalu diaktifkan, pembacaan terjadi ketika alamat sel diubah. Menulis dan membaca dapat dilakukan secara berurutan dalam beberapa sel berturut-turut, mulai dari alamat adr_start tertentu, direkam di tepi terdepan sinyal adr_write, dan satu sel di alamat sewenang-wenang (akses acak).
Pada MyHDL, kode ini terlihat seperti ini (sinyal tulis dan baca datang dalam logika terbalik):
from myhdl import * @block def ram_driver(data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we):
Jika dikonversi ke Verilog menggunakan fungsi:
def convert(hdl): data_memory = TristateSignal(intbv(0)[8:]) data_in = Signal(intbv(0)[8:]) data_out = Signal(intbv(0)[8:]) adr = Signal(intbv(0)[16:]) adr_start = Signal(intbv(0)[16:]) adr_write = Signal(bool(0)) read, write, we = [Signal(bool(1)) for i in range(3)] inst = ram_driver(data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we) inst.convert(hdl=hdl) convert(hdl='Verilog')
maka kita mendapatkan yang berikut ini:
`timescale 1ns/10ps module ram_driver ( data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we ); input [7:0] data_in; output [7:0] data_out; reg [7:0] data_out; output [15:0] adr; reg [15:0] adr; input [15:0] adr_start; input adr_write; inout [7:0] data_memory; wire [7:0] data_memory; input read; input write; output we; reg we; reg [7:0] mem_z; assign data_memory = mem_z; always @(write) begin: RAM_DRIVER_WRITE_DATA if ((!write)) begin mem_z <= data_in; we <= 0; end else begin mem_z <= 'bz; data_out <= data_memory; we <= 1; end end always @(posedge adr_write, posedge write, negedge read) begin: RAM_DRIVER_WRITE_START_ADR if (adr_write) begin adr <= adr_start; end else begin adr <= (adr + 1); end end endmodule
Tidak perlu mengubah proyek ke Verilog untuk simulasi, langkah ini akan diperlukan untuk menginstal FPGA.
Pemodelan
Setelah uraian logika, proyek harus diverifikasi. Anda dapat membatasi diri Anda, misalnya, untuk mensimulasikan pengaruh input dan melihat respons modul dalam diagram waktu. Tetapi dengan opsi ini, akan lebih sulit untuk memprediksi interaksi modul Anda dengan chip memori. Oleh karena itu, untuk sepenuhnya memverifikasi operasi perangkat yang dibuat, Anda perlu membuat model memori dan menguji interaksi antara kedua perangkat ini.
Karena pekerjaan berlangsung dalam python, jenis kamus yang diberikan (kamus) menyarankan dirinya untuk model memori. Data yang disimpan sebagai {key: value}, dan untuk kasus ini {address: data}.
memory = { 0: 123, 1: 456, 2: 789 } memory[0] >> 123 memory[1] >> 456
Untuk tujuan yang sama, tipe data daftar cocok, di mana setiap elemen memiliki koordinat sendiri yang menunjukkan lokasi elemen dalam daftar:
memory = [123, 456, 789] memory[0] >> 123 memory[1] >> 456
Menggunakan kamus untuk mensimulasikan memori tampaknya lebih disukai mengingat visibilitas yang lebih besar.
Deskripsi shell pengujian (dalam file test_seq_access.py) dimulai dengan pengumuman sinyal, inisialisasi kondisi awal dan melemparkannya ke fungsi driver memori yang dijelaskan di atas:
@block def testbench(): data_memory = TristateSignal(intbv(0)[8:]) data_in = Signal(intbv(0)[8:]) data_out = Signal(intbv(0)[8:]) adr = Signal(intbv(0)[16:]) adr_start = Signal(intbv(20)[16:]) adr_write = Signal(bool(0)) read, write, we = [Signal(bool(1)) for i in range(3)] ram = ram_driver(data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we)
Berikut ini menjelaskan model memori. Status awal diinisialisasi, secara default memori diisi dengan nilai nol. Batasi model memori hingga 128 sel:
memory = {i: intbv(0) for i in range(128)}
dan jelaskan perilaku memori: ketika KAMI dalam kondisi rendah, tulis nilai di baris ke alamat memori yang sesuai, jika tidak, model memberikan nilai pada alamat yang diberikan:
mem_z = data_memory.driver() @always_comb def access(): if not we: memory[int(adr.val)] = data_memory.val if we: data_out.next = memory[int(adr.val)] mem_z.next = None
Kemudian, dalam fungsi yang sama, Anda dapat menggambarkan perilaku sinyal input (untuk kasus sequential write / read): alamat awal dicatat → 8 sel informasi direkam → alamat awal direkam → 8 sel informasi yang direkam dibaca.
@instance def stimul(): init_adr = random.randint(0, 50) # yield delay(100) write.next = 1 adr_write.next = 1 adr_start.next = init_adr # yield delay(100) adr_write.next = 0 yield delay(100) for i in range(8): # 8 write.next = 0 data_in.next = random.randint(0, 100) yield delay(100) write.next = 1 yield delay(100) adr_start.next = init_adr # adr_write.next = 1 yield delay(100) adr_write.next = 0 yield delay(100) for i in range(8): # read.next = 0 yield delay(100) read.next = 1 yield delay(100) raise StopSimulation return stimul, ram, access
Jalankan simulasi:
tb = testbench() tb.config_sim(trace=True) tb.run_sim()
Setelah memulai program, file testbench_seq_access.vcd dihasilkan di folder yang berfungsi, buka di gtkwave:
gtkwave testbench_seq_access.vcd
Dan kita melihat gambar:

Informasi yang direkam berhasil dibaca.
Anda dapat melihat isi memori dengan menambahkan kode berikut ke testbench:
for key, value in memory.items(): print('adr:{}'.format(key), 'data:{}'.format(value))
Berikut ini muncul di konsol:

Pengujian
Setelah itu, Anda dapat melakukan beberapa tes otomatis dengan peningkatan jumlah sel yang dapat ditulis / dibaca. Untuk melakukan ini, beberapa siklus uji dan kamus dummy ditambahkan ke dalam testbench, di mana informasi tertulis dan dapat dibaca serta konstruk tegas ditambahkan, yang menyebabkan kesalahan jika dua kamus tidak sama:
@instance def stimul(): for time in range(100): temp_mem_write = {} temp_mem_read = {} init_adr = random.randint(0, 50) yield delay(100) write.next = 1 adr_write.next = 1 adr_start.next = init_adr yield delay(100) adr_write.next = 0 yield delay(100) for i in range(64): write.next = 0 data_in.next = random.randint(0, 100) temp_mem_write[i] = int(data_in.next) yield delay(100) write.next = 1 yield delay(100) adr_start.next = init_adr adr_write.next = 1 yield delay(100) adr_write.next = 0 yield delay(100) for i in range(64): read.next = 0 temp_mem_read[i] = int(data_out.val) yield delay(100) read.next = 1 yield delay(100) assert temp_mem_write == temp_mem_read, " " for key, value in memory.items(): print('adr:{}'.format(key), 'data:{}'.format(value)) raise StopSimulation return stimul, ram, access
Selanjutnya, Anda dapat membuat testbench kedua untuk menguji operasi dalam mode akses acak: test_random_access.py.
Gagasan dari tes kedua serupa: kami menulis informasi acak pada alamat acak dan menambahkan pasangan {address: data} ke kamus temp_mem_write. Kemudian kita melihat-lihat alamat dalam kamus ini dan membaca informasi dari memori, memasukkannya ke dalam kamus temp_mem_read. Dan di akhir dengan konstruk yang tegas, kami memeriksa isi dari dua kamus.
import random from myhdl import * from ram_driver import ram_driver @block def testbench_random_access(): data_memory = TristateSignal(intbv(0)[8:]) data_in = Signal(intbv(0)[8:]) data_out = Signal(intbv(0)[8:]) adr = Signal(intbv(0)[16:]) adr_start = Signal(intbv(20)[16:]) adr_write = Signal(bool(0)) read, write, we = [Signal(bool(1)) for i in range(3)] ram = ram_driver(data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we) memory ={i:intbv(0) for i in range(128)} mem_z = data_memory.driver() @always_comb def access(): if not we: memory[int(adr.val)] = data_memory.val if we: data_out.next = memory[int(adr.val)] mem_z.next = None @instance def stimul(): for time in range(10): temp_mem_write = {} temp_mem_read = {} yield delay(100) for i in range(64): write.next = 1 adr_write.next = 1 adr_start.next = random.randint(0, 126) yield delay(100) adr_write.next = 0 yield delay(100) write.next = 0 data_in.next = random.randint(0, 100) temp_mem_write[int(adr_start.val)] = int(data_in.next) yield delay(100) write.next = 1 yield delay(100) for key in temp_mem_write.keys(): adr_start.next = key adr_write.next = 1 yield delay(100) adr_write.next = 0 yield delay(100) read.next = 0 temp_mem_read[key] = int(data_out.val) yield delay(100) read.next = 1 yield delay(100) assert temp_mem_write == temp_mem_read, ' random access' raise StopSimulation return stimul, ram, access tb = testbench_random_access() tb.config_sim(trace=True) tb.run_sim()
Python memiliki beberapa kerangka kerja untuk mengotomatisasi pelaksanaan pengujian. Saya akan mengambil pytest untuk kesederhanaan, itu harus diinstal dari pip:
pip3 install pytest
Ketika perintah "pysest" diluncurkan dari konsol, kerangka kerja akan menemukan dan mengeksekusi semua file dalam folder yang berfungsi dengan "test_ *" pada namanya.

Tes berhasil diselesaikan. Saya akan dengan sengaja membuat kesalahan dalam uraian perangkat:
@block def ram_driver(data_in, data_out, adr, adr_start, adr_write, data_memory, read, write, we): mem_z = data_memory.driver() @always(adr_write.posedge, write.posedge, read.negedge) def write_start_adr(): if adr_write: adr.next = adr_start else: adr.next = adr + 1 @always(write) def write_data(): if not write: mem_z.next = data_in we.next = 1 # , else: mem_z.next = None data_out.next = data_memory we.next = 1
Saya menjalankan tes:

Seperti yang diharapkan, dalam kedua tes, informasi awal (nol) dipertimbangkan, yaitu informasi baru tidak direkam.
Kesimpulan
Menggunakan python bersama dengan myHDL memungkinkan Anda untuk mengotomatisasi pengujian firmware yang dikembangkan untuk FPGA dan membuat hampir semua lingkungan pengujian menggunakan kemampuan bahasa pemrograman python yang kaya.
Artikel tersebut mempertimbangkan:
- membuat modul yang berfungsi dengan memori;
- membuat model memori;
- pembuatan case uji;
- otomasi uji dengan kerangka pytest.