Prosesor lunak FPGA asli dengan kompiler bahasa tingkat tinggi atau Song of the Mouse

Prosesor lunak FPGA sendiri dengan kompiler bahasa tingkat tinggi atau Song of the Mouse - pengalaman dalam mengadaptasi kompiler bahasa tingkat tinggi ke inti prosesor tumpukan.

Masalah umum untuk prosesor perangkat lunak adalah kurangnya alat pengembangan untuk mereka, terutama jika sistem instruksi mereka bukan bagian dari instruksi dari salah satu inti prosesor populer mereka. Pengembang dalam hal ini harus menyelesaikan masalah ini. Solusi langsungnya adalah membuat kompiler bahasa assembler. Namun, dalam kenyataan modern tidak selalu nyaman untuk bekerja di Assembler, karena dalam proses pengembangan proyek sistem perintah dapat berubah karena, misalnya, mengubah persyaratan. Oleh karena itu, tugas implementasi yang mudah dari kompiler bahasa tingkat tinggi (JAV) untuk prosesor perangkat lunak adalah relevan.

Compiler Python - Uzh tampaknya menjadi toolkit yang mudah dan nyaman untuk mengembangkan perangkat lunak untuk prosesor perangkat lunak. Toolkit untuk mendefinisikan primitif dan makro sebagai fungsi dari bahasa target memungkinkan tempat-tempat penting untuk diimplementasikan dalam bahasa assembly prosesor. Makalah ini membahas poin utama adaptasi kompiler untuk prosesor arsitektur stack.

Alih-alih sebuah epigraf:

Jika Anda mengambil mouse dewasa
dan, dengan hati-hati memegangnya,
masukkan jarum ke dalamnya
Anda akan mendapatkan landak.

Jika landak ini,
Hidung tersumbat, agar tidak bernafas,
Dimana lebih dalam, buang ke sungai
Anda akan mendapat keributan.

Jika ini kasar, Pegang
kepala
Anda di wakil, Tarik lebih keras dengan ekor
Anda akan mendapatkan ular.

Jika ini sudah,
Setelah menyiapkan dua pisau ...
Namun, dia mungkin akan mati,
Tapi idenya bagus!


pengantar


Dalam banyak kasus, ketika menerapkan instrumen pengukuran, peralatan penelitian, lebih disukai untuk menggunakan solusi FPGA / FPGA yang dapat dikonfigurasi ulang sebagai inti utama sistem. Pendekatan ini memiliki banyak keunggulan, karena kemampuannya dengan mudah dan cepat membuat perubahan pada logika kerja, serta karena akselerasi perangkat keras pemrosesan data dan sinyal kontrol.

Untuk berbagai tugas, seperti pemrosesan sinyal digital, sistem kontrol tertanam, akuisisi data dan sistem analisis, pendekatan tersebut telah membuktikan dirinya, yang terdiri dari penggabungan dalam satu blok solusi yang diimplementasikan oleh logika FPGA untuk proses kritis dan elemen kontrol program berdasarkan pada satu atau beberapa prosesor perangkat lunak untuk manajemen dan koordinasi umum, serta untuk mengimplementasikan interaksi dengan pengguna atau perangkat / node eksternal. Penggunaan prosesor perangkat lunak dalam hal ini memungkinkan kami untuk sedikit mengurangi waktu yang dihabiskan untuk debugging dan verifikasi algoritma kontrol sistem atau algoritma interaksi dari masing-masing node.

Daftar Keinginan Khas


Seringkali, prosesor lunak dalam hal ini tidak memerlukan kinerja sangat tinggi (karena lebih mudah untuk dicapai, saya menggunakan sumber daya logis dan perangkat keras FPGA). Mereka bisa sangat sederhana (dan dari sudut pandang mikrokontroler modern - hampir primitif), karena mereka dapat melakukannya tanpa sistem interupsi yang rumit, hanya bekerja dengan node atau antarmuka tertentu, tidak perlu mendukung sistem perintah tertentu. Mungkin ada banyak dari mereka, sementara masing-masing dari mereka hanya dapat menjalankan satu set algoritma atau subprogram tertentu. Kapasitas prosesor lunak juga dapat berupa apa saja, termasuk bukan kelipatan satu byte - tergantung pada persyaratan tugas saat ini.

Sasaran umum untuk prosesor lunak adalah:

  • fungsionalitas yang memadai dari sistem perintah, mungkin dioptimalkan untuk tugas tersebut;
  • , .. ;
  • – , .

Tentu saja, masalah untuk prosesor perangkat lunak adalah kurangnya alat pengembangan untuk mereka, terutama jika sistem instruksi mereka bukan bagian dari instruksi dari salah satu inti prosesor populer mereka. Pengembang dalam hal ini harus menyelesaikan masalah ini. Solusi langsungnya adalah membuat kompiler bahasa assembler untuk prosesor perangkat lunak. Namun, dalam kenyataan modern tidak selalu nyaman untuk bekerja di Assembler, terutama jika sistem tim berubah selama pengembangan proyek karena, misalnya, perubahan persyaratan. Oleh karena itu, logis untuk menambahkan persyaratan di atas persyaratan implementasi yang mudah dari kompiler bahasa tingkat tinggi (HLV) untuk prosesor lunak.

Komponen sumber


Pemroses tumpukan memenuhi persyaratan ini dengan persentase kepatuhan yang tinggi, seperti tidak perlu alamat register, kedalaman bit perintah mungkin kecil.
Kedalaman bit data untuk mereka dapat bervariasi dan tidak terikat pada kedalaman bit dari sistem perintah. Menjadi implementasi perangkat keras de facto (walaupun dengan beberapa peringatan) dari representasi perantara kode program selama kompilasi (mesin virtual stacked, atau dalam hal tata bahasa bebas konteks - otomat toko), dimungkinkan dengan biaya tenaga kerja yang rendah untuk menerjemahkan tata bahasa dari setiap bahasa menjadi kode yang dapat dieksekusi. Selain itu, untuk prosesor tumpukan, bahasa Fort praktis adalah bahasa “asli”. Biaya tenaga kerja untuk mengimplementasikan kompiler Fort untuk prosesor tumpukan dapat dibandingkan dengan Assembler, dengan fleksibilitas dan efisiensi yang jauh lebih besar dalam implementasi program di masa depan.

Memiliki tugas membangun sistem untuk mengumpulkan data dari sensor pintar dalam mode mendekati waktu nyata, prosesor Fort dipilih sebagai solusi referensi (yang disebut Desain Referensi) dari prosesor lunak, yang dijelaskan dalam [ 1 ] (selanjutnya akan menjadi kadang-kadang disebut sebagai prosesor whiteTiger dengan nama panggilan penulisnya).

Fitur utamanya:

  • Pisahkan data dan kembalikan tumpukan
  • Arsitektur organisasi memori Harvard (program terpisah dan memori data, termasuk ruang alamat);
  • ekspansi dengan periferal menggunakan bus paralel sederhana.
  • Prosesor tidak menggunakan pipa, pelaksanaan perintah adalah push-pull:

    1. ambil perintah dan operan;
    2. pelaksanaan perintah dan menyimpan hasilnya.

Prosesor ini dilengkapi dengan kode program UART-loader, yang memungkinkan Anda untuk mengubah program yang dapat dieksekusi tanpa mengkompilasi ulang proyek untuk FPGA.

Berkenaan dengan konfigurasi memori blok dalam FPGA, kapasitas instruksi diatur ke 9 bit. Kedalaman bit data diatur ke 32 bit, tetapi pada dasarnya bisa apa saja.

Kode prosesor ditulis dalam VHDL tanpa menggunakan perpustakaan tertentu, yang memungkinkan Anda untuk bekerja dengan proyek ini pada FPGA dari produsen mana pun.

Untuk penggunaan yang relatif luas, menurunkan "ambang input", serta untuk menggunakan kembali kode dan menerapkan pengembangan kode, lebih baik untuk beralih ke mesin Java selain Fort (ini sebagian disebabkan oleh takhayul dan kesalahpahaman pemrogram stream-stream mengenai kompleksitas bahasa ini dan keterbacaan kode tersebut. (Omong-omong, salah satu penulis karya ini memiliki pendapat yang sama tentang bahasa mirip-C)).

Berdasarkan sejumlah faktor, bahasa Python (Python) dipilih untuk percobaan untuk "mengikat" prosesor perangkat lunak dan Java Language Engine. Ini adalah bahasa pemrograman tujuan umum tingkat tinggi yang berfokus pada peningkatan produktivitas pengembang dan keterbacaan kode, mendukung beberapa paradigma pemrograman, termasuk struktural, berorientasi objek, fungsional, imperatif, dan berorientasi aspek [ 2]

Untuk pengembang pemula, ekstensi MyHDL [ 3 , 4 ] menarik , yang memungkinkan menggambarkan elemen dan struktur perangkat keras dalam Python dan menerjemahkannya ke dalam kode VHDL atau Verilog.

Beberapa waktu lalu, kompiler Uzh [ 5 ] diumumkan - kompiler kecil untuk prosesor perangkat lunak Zmey FPGA (arsitektur tumpukan 32-bit dengan dukungan multithreading - jika Anda mengikuti rantai versi / modifikasi / verifikasi - Zmey adalah keturunan yang jauh dari prosesor whiteTiger).
Uzh juga merupakan himpunan bagian yang disusun secara statis dari Python, berdasarkan pada raddsl toolkit yang menjanjikan (seperangkat alat untuk membuat prototipe kompiler DSL dengan cepat) [ 6 , 7 ].

Dengan demikian, faktor-faktor yang mempengaruhi pemilihan arah kerja dapat dirumuskan kira-kira seperti ini:

  • minat pada alat yang menurunkan "ambang batas" untuk pengembang perangkat dan sistem pemula pada FPGA (secara sintaksis Python tidak "menakutkan" bagi pemula seperti VHDL);
  • berjuang untuk keharmonisan dan gaya tunggal dalam proyek (secara teori dimungkinkan untuk menggambarkan blok perangkat keras yang diperlukan dan perangkat lunak dari prosesor perangkat lunak dengan Python);
  • kebetulan acak.

Nuansa kecil, “hampir” tidak berarti


Kode sumber prosesor Zmey tidak terbuka, tetapi deskripsi tentang prinsip-prinsip operasinya dan beberapa fitur arsitektur tersedia. Meskipun juga dapat ditumpuk, ada sejumlah perbedaan utama dari prosesor whiteTiger:

  • tumpukan adalah perangkat lunak - mis. diwakili oleh pointer dan ditempatkan dalam memori data di alamat yang berbeda;
  • , - ;
  • ;
  • , .

Karenanya, kompiler Uzh mempertimbangkan fitur-fitur ini. Kompiler menerima kode Python dan menghasilkan aliran boot pada output untuk memulai memori program dan memori data prosesor, titik kuncinya adalah bahwa semua fungsi bahasa tersedia pada tahap kompilasi.

Untuk menginstal kompilator Uzh, cukup unduh arsipnya dan unzip ke folder yang nyaman (lebih baik mematuhi rekomendasi umum untuk perangkat lunak khusus - untuk menghindari jalur yang mengandung Cyrillic dan spasi). Anda juga perlu mengunduh dan membuka zip toolkit raddsl ke folder utama kompiler.

Folder tes kompiler berisi contoh program untuk prosesor lunak, folder src berisi teks sumber elemen kompiler. Untuk kenyamanan, lebih baik membuat file batch kecil (ekstensi .cmd) dengan konten :, di c.py C:\D\My_Docs\Documents\uzh-master\tests\abc.py mana abc.py adalah nama file dengan program untuk prosesor lunak.

Seekor ular menggigit ekornya atau memukul-mukul besi dan perangkat lunak


Untuk mengadaptasi Uzh ke prosesor whiteTiger, beberapa perubahan akan diperlukan, serta prosesor itu sendiri harus sedikit diperbaiki.

Untungnya, tidak banyak tempat yang harus disesuaikan dalam kompiler. File utama "tergantung perangkat keras":

  • asm.py - assembler dan pembentukan angka (literal);
  • gen.py - aturan pembuatan kode tingkat rendah (fungsi, variabel, transisi dan kondisi);
  • stream.py - membentuk aliran boot;
  • macro.py - definisi makro, pada kenyataannya - ekstensi bahasa dasar dengan fungsi khusus perangkat keras.

Dalam desain prosesor whiteTiger asli, loader UART hanya menginisialisasi memori program. Algoritma bootloader sederhana, tetapi mapan dan andal:

  • setelah menerima byte kontrol tertentu, loader menetapkan level aktif pada jalur internal reset prosesor;
  • perintah byte kedua me-reset penghitung alamat memori;
  • berikut ini adalah urutan notebook dari kata yang dikirimkan, dimulai dengan yang termuda, dikombinasikan dengan nomor notebook;
  • setelah setiap byte dengan buku catatan yang penuh, sepasang byte kontrol mengikuti, yang pertama menetapkan tingkat aktif pada garis izin tulis memori, yang kedua me-reset itu;
  • setelah menyelesaikan urutan notebook yang dikemas, level aktif pada garis reset dihapus oleh byte kontrol.

Karena kompiler juga menggunakan memori data, maka perlu untuk memodifikasi loader sehingga juga dapat menginisialisasi memori data.

Karena memori data terlibat dalam logika inti prosesor, maka perlu untuk menggandakan data dan jalur kontrolnya. Untuk ini, sinyal tambahan DataDinBtemp, LoaderAddrB, DataWeBtemp diperkenalkan - data, alamat dan resolusi perekaman untuk port dalam memori.

Kode bootloader sekarang terlihat seperti ini:

uart_unit: entity work.uart
--uart_unit: entity uart
  Generic map(
    ClkFreq => 50_000_000,
    Baudrate => 115200)
  port map(
    clk => clk,
    rxd => rx,
    txd => tx,
    dout => receivedByte,
    received => received,
    din => transmitByte,
    transmit => transmit);
    
process(clk)
begin
  if rising_edge(clk) then
    if received = '1' then
      case conv_integer(receivedByte) is
      -- 0-F   - 0-3 bits
        when 0 to 15 => CodeDinA(3 downto 0) <= receivedByte(3 downto 0);
		                  DataDinBtemp(3 downto 0) <= receivedByte(3 downto 0);
      -- 10-1F -4-7bits
        when 16 to 31 => CodeDinA(7 downto 4) <= receivedByte(3 downto 0);
		                   DataDinBtemp(7 downto 4) <= receivedByte(3 downto 0); 
      -- 20-2F -8bit 
        when 32 to 47 => CodeDinA(8) <= receivedByte(0);
	                   DataDinBtemp(11 downto 8) <= receivedByte(3 downto 0);
	  when 48 to 63 => DataDinBtemp(15 downto 12) <= receivedByte(3 downto 0);
	  when 64 to 79 => DataDinBtemp(19 downto 16) <= receivedByte(3 downto 0);
	  when 80 to 95 => DataDinBtemp(23 downto 20) <= receivedByte(3 downto 0);
	  when 96 to 111 => DataDinBtemp(27 downto 24) <= receivedByte(3 downto 0);
        when 112 to 127 => DataDinBtemp(31 downto 28) <= receivedByte(3 downto 0);

      -- F0 addr=0
        when 240 => CodeAddrA <= (others => '0');
      -- F1 - WE=1
        when 241 => CodeWeA <= '1';
      -- F2 WE=0 addr++
        when 242 => CodeWeA <= '0'; CodeAddrA <= CodeAddrA + 1;
      -- F3 RESET=1
        when 243 => int_reset <= '1';
      -- F4 RESET=0
        when 244 => int_reset <= '0';

      -- F5 addr=0
        when 245 => LoaderAddrB <= (others => '0');
      -- F6 - WE=1
        when 246 => DataWeBtemp <= '1';
      -- F7 WE=0 addr++
        when 247 => DataWeBtemp <= '0'; LoaderAddrB <= LoaderAddrB + 1;
		  
		  
        when others => null;
      end case;
    end if;
  end if;
end process;

---- end of loader


Dengan level reset aktif, sinyal DataDinBtemp, LoaderAddrB, DataWeBtemp terhubung ke port memori data yang sesuai.

if reset = '1' or int_reset = '1' then
      DSAddrA <= (others => '0');      
      
      RSAddrA <= (others => '0');
      RSAddrB <= (others => '0');
      RSWeA <= '0';
      
      DataAddrB <= LoaderAddrB;
		DataDinB<=DataDinBtemp;
		DataWeB<=DataWeBtemp;
      DataWeA <= '0';

Sesuai dengan algoritma bootloader, perlu untuk memodifikasi modul stream.py. Sekarang ia memiliki dua fungsi. Fungsi pertama - get_val () - membagi kata input ke jumlah tetrads yang diinginkan. Jadi, untuk instruksi 9-bit dari prosesor whiteTiger, mereka akan diubah menjadi kelompok tiga tetrad, dan data 32-bit dalam urutan delapan tetrad. Fungsi kedua make () membentuk bootstrap secara langsung.
Bentuk akhir dari modul stream:

def get_val(x, by_4):
  r = []
  for i in range(by_4):
    r.append((x & 0xf) | (i << 4))
    x >>= 4
  return r

def make(code, data, core=0):
  #        0  
  stream = [243,245] 
  for x in data:
    #    32- 
    #         
    stream += get_val(x, 8) + [246, 247]
  #       0
  stream += [240]
  for x in code:
    #    9-  
    #         
    stream += get_val(x, 3) + [241, 242]
  #  
  stream.append(244)

  return bytearray(stream)


Perubahan berikut dalam kompiler akan memengaruhi modul asm.py, yang menjelaskan sistem perintah prosesor (mnemonics perintah dan opcode perintah ditulis) dan cara mewakili / menyusun nilai numerik - literal.

Perintah dikemas ke dalam kamus, dan fungsi lite () bertanggung jawab untuk literal. Jika semuanya sederhana dengan sistem perintah - daftar mnemonik dan opcodes yang sesuai hanya berubah, maka situasi dengan literal sedikit berbeda. Prosesor Zmey memiliki instruksi 8-bit dan ada sejumlah instruksi khusus untuk bekerja dengan literal. Dalam whiteTiger, bit ke-9 menunjukkan apakah opcode adalah perintah atau bagian dari angka.

Jika bit tertinggi (ke-9) dari sebuah kata adalah 1, maka opcode ditafsirkan sebagai angka - misalnya, empat opcode berturut-turut dengan tanda angka membentuk angka 32-bit sebagai hasilnya. Tanda akhir angka adalah kehadiran opcode perintah - untuk kepastian dan memastikan keseragaman, akhir dari penentuan angka adalah opcode dari perintah NOP (“no operations”).

Akibatnya, fungsi lit () yang dimodifikasi terlihat seperti ini:


def lit(x):
  x &= 0xffffffff
  r = [] 
  if (x>>24) & 255 :
    r.append(int((x>>24) & 255) | 256)
  if (x>>16) & 255:
    r.append(int((x>>16) & 255) | 256)
  if (x>>8) & 255:
    r.append(int((x>>8) & 255) | 256)
  r.append(int(x & 255) | 256)
  r += asm("NOP")
  return list(r)


Perubahan / definisi utama dan paling penting ada di modul gen.py. Modul ini mendefinisikan logika dasar pekerjaan / eksekusi kode tingkat tinggi di tingkat assembler:

  • lompatan bersyarat dan tanpa syarat;
  • memanggil fungsi dan menyampaikan argumen kepada mereka;
  • kembali dari fungsi dan mengembalikan hasil;
  • penyesuaian ukuran memori program, memori data dan tumpukan;
  • urutan tindakan saat startup

Untuk mendukung Java, prosesor harus dapat bekerja secara sewenang-wenang dengan memori dan pointer dan memiliki area memori untuk menyimpan fungsi variabel lokal.

Dalam prosesor Zmey, tumpukan kembali digunakan untuk bekerja dengan variabel lokal dan argumen fungsi - argumen fungsi ditransfer ke sana dan selama pekerjaan lebih lanjut, mereka diakses melalui register-pointer tumpukan kembali (baca, modifikasi atas / bawah, baca di alamat pointer). Karena tumpukan secara fisik terletak di memori data, operasi tersebut pada dasarnya hanya turun ke operasi memori, dan variabel global terletak di dalam memori yang sama.

Dalam whiteTiger, return dan tumpukan data adalah tumpukan perangkat keras khusus dengan ruang alamat mereka dan tidak memiliki petunjuk penunjuk tumpukan. Konsekuensinya, operasi dengan melewatkan argumen ke fungsi dan bekerja dengan variabel lokal perlu diatur melalui memori data. Tidak masuk akal untuk meningkatkan volume tumpukan data dan mengembalikan kemungkinan penyimpanan array data yang relatif besar di dalamnya, lebih logis untuk memiliki memori data yang sedikit besar.

Untuk bekerja dengan variabel lokal, register LocalReg khusus ditambahkan, tugasnya adalah menyimpan pointer ke area memori yang dialokasikan untuk variabel lokal (semacam heap). Juga menambahkan operasi untuk bekerja dengannya (file cpu.vhd - area definisi perintah):


          -- group 1; pop 0; push 1;
          when cmdLOCAL => DSDinA <= LocalReg;
			 when cmdLOCALadd => DSDinA <= LocalReg; LocalReg <= LocalReg+1;
			 when cmdLOCALsubb => DSDinA <= LocalReg; LocalReg <= LocalReg-1;
          -- group 2; pop 1; push 0;
          when cmdSETLOCAL => LocalReg <= DSDinA;

LOCAL - kembali ke tumpukan data nilai saat ini dari pointer LocalReg;
SETLOCAL - mengatur nilai pointer baru yang diterima dari tumpukan data;
LOCALadd - meninggalkan nilai saat ini dari pointer pada tumpukan data dan menambahnya dengan 1;
LOCALsubb - meninggalkan nilai pointer saat ini pada tumpukan data dan menguranginya dengan 1.
LOCALadd dan LOCALsubb ditambahkan untuk mengurangi jumlah kutu selama operasi parameter fungsi yang lewat dan sebaliknya.

Tidak seperti whiteTiger asli, koneksi memori data sedikit berubah - sekarang port memori Dalam secara konstan ditangani oleh output dari sel pertama dari tumpukan data, output dari sel kedua dari tumpukan data diumpankan ke inputnya:

-- ++
DataAddrB <= DSDoutA(DataAddrB'range);
DataDinB <= DSDoutB;

Logika untuk mengeksekusi perintah STORE dan FETCH juga telah sedikit dikoreksi - FETCH menerima nilai output port Dalam memori di bagian atas tumpukan data, dan STORE hanya mengontrol sinyal tuliskan untuk port B:

-- group 3; pop 1; push 1;
          when cmdFETCH => DSDinA <= DataDoutB;
          when cmdSTORE =>            
            DataWeB <= '1';

Sebagai bagian dari pelatihan, serta untuk beberapa dukungan perangkat keras untuk loop pada level rendah (dan pada level kompiler dari bahasa Fort), setumpuk counter loop ditambahkan ke inti whiteTiger (tindakan serupa dengan yang ketika mendeklarasikan data dan mengembalikan tumpukan):

--  
type TCycleStack is array(0 to LocalSize-1) of DataSignal;
signal CycleStack: TCycleStack;
signal CSAddrA, CSAddrB: StackAddrSignal;
signal CSDoutA, CSDoutB: DataSignal;
signal CSDinA, CSDinB: DataSignal;
signal CSWeA, CSWeB: std_logic;
--  
process(clk)
begin
  if rising_edge(clk) then
    if CSWeA = '1' then
      CycleStack(conv_integer(CSAddrA)) <= CSDinA;
      CSDoutA <= CSDinA;
    else
      CSDoutA <= CycleStack(conv_integer(CSAddrA));
    end if;
  end if;
end process;


Perintah penghitung siklus telah ditambahkan.

DO - memindahkan jumlah iterasi siklus dari tumpukan data ke tumpukan counter dan menempatkan nilai yang bertambah dari penghitung instruksi pada tumpukan kembali.

LOOP - memeriksa penghitung penghitung, jika tidak tercapai, elemen teratas dari tumpukan counter dikurangi, transisi ke alamat di bagian atas tumpukan kembali dilakukan. Jika bagian atas tumpukan counter adalah nol, elemen atas diatur ulang, alamat kembali ke awal siklus dari atas tumpukan kembali juga diatur ulang.


	when cmdDO => -- DO - 
               RSAddrA <= RSAddrA + 1; -- 
               RSDinA <= ip + 1;
               RSWeA <= '1';
				
               CSAddrA <= CSAddrA + 1; --
         		CSDinA <= DSDoutA;
 		         CSWeA <= '1';
		         DSAddrA <= DSAddrA - 1; --
		         ip <= ip + 1;	-- 

      when cmdLOOP => --            
           if conv_integer(CSDoutA) = 0 then
	          ip <= ip + 1;	-- 
		         RSAddrA <= RSAddrA - 1; -- 
		         CSAddrA <= CSAddrA - 1; -- 
            else
		         CSDinA <= CSDoutA - 1;
		         CSWeA <= '1';
		         ip <= RSDoutA(ip'range);
            end if;
			 

Sekarang Anda dapat mulai memodifikasi kode untuk modul gen.py.

* _SIZE variabel tidak memerlukan komentar dan hanya membutuhkan substitusi nilai yang ditentukan dalam proyek inti prosesor.

Daftar STUB adalah tulisan rintisan sementara untuk membuat tempat untuk alamat transisi dan kemudian mengisinya dengan kompiler (nilai saat ini sesuai dengan ruang alamat 24-bit dari memori kode).

Daftar STARTUP - mengatur urutan tindakan yang dilakukan oleh kernel setelah reset - dalam hal ini, alamat awal memori variabel lokal diatur ke 900, dan transisi ke titik awal (jika Anda tidak mengubah apa pun, titik awal / masuk dalam aplikasi ditulis ke kompiler di alamat memori data) 2):

STARTUP = asm("""
900  SETLOCAL
2 NOP FETCH JMP
""")

Definisi func () menetapkan tindakan yang dilakukan ketika fungsi dipanggil, yaitu, transfer argumen fungsi ke wilayah variabel lokal, alokasi memori untuk variabel lokal sendiri dari fungsi.

@act
def func(t, X):
  t.c.entry = t.c.globs[X]
  t.c.entry["offs"] = len(t.c.code) # - 1
  args = t.c.entry["args"]
  temps_size = len(t.c.entry["locs"]) - args
#      
  t.out = asm("LOCALadd STORE " * args)
  if temps_size:
#      
    t.out += asm("LOCAL %d PLUS SETLOCAL" % temps_size)
  return True

Epilog () mendefinisikan tindakan ketika kembali dari suatu fungsi - membebaskan memori dari variabel sementara, kembali ke titik panggilan.

def epilog(t, X):
  locs_size = len(t.c.entry["locs"])
#    
  t.out = asm("RET")
  if locs_size:
#    ()  
    t.out = asm("LOCAL %d MINUS SETLOCAL" % locs_size) + t.out
  return True


Bekerja dengan variabel dilakukan melalui alamat mereka, definisi kunci untuk ini adalah push_local (), yang meninggalkan alamat variabel "tingkat tinggi" pada tumpukan data.

def push_local(t, X):
#          
#  
  t.out = asm("LOCAL %d MINUS" % get_loc_offset(t, X))
  return True

Poin-poin kunci berikut adalah transisi kondisional dan tanpa syarat. Lompatan bersyarat pada prosesor whiteTiger memeriksa elemen kedua dari tumpukan data untuk 0 dan melompat ke alamat di bagian atas tumpukan jika kondisi terpenuhi. Lompatan tanpa syarat hanya menetapkan nilai penghitung instruksi ke nilai di bagian atas tumpukan.

@act
def goto_if_0(t, X):
  push_label(t, X)
  t.out += asm("IF")
  return True

@act
def goto(t, X):
  push_label(t, X)
  t.out += asm("JMP")
  return True


Dua definisi berikut ini menentukan operasi bit shift - hanya pada level rendah, loop diterapkan (ini akan memberikan beberapa keuntungan dalam ukuran kode - dalam aslinya, kompiler hanya menempatkan jumlah operasi shift elementer yang diperlukan dalam satu baris.

@act
def shl_const(t, X):
  t.out = asm("%d DO SHL LOOP" %(X-1))
  return True

@act
def shr_const(t, X):
  t.out = asm("%d DO SHR LOOP" %(X-1))
  return True

Dan definisi utama dari kompiler pada level rendah adalah seperangkat aturan untuk operasi bahasa dan bekerja dengan memori:

stmt = rule(alt(
  seq(Push(Int(X)), to(lambda v: asm("%d" % v.X))),
  seq(Push(Local(X)), push_local),
  seq(Push(Global(X)), push_global),
  seq(Load(), to(lambda v: asm("NOP FETCH"))),
  seq(Store(), to(lambda v: asm("STORE"))),
  seq(Call(), to(lambda v: asm("CALL"))),
  seq(BinOp("+"), to(lambda v: asm("PLUS"))),
  seq(BinOp("-"), to(lambda v: asm("MINUS"))),
  seq(BinOp("&"), to(lambda v: asm("AND"))),
  seq(BinOp("|"), to(lambda v: asm("OR"))),
  seq(BinOp("^"), to(lambda v: asm("XOR"))),
  seq(BinOp("*"), to(lambda v: asm("MUL"))),
  seq(BinOp("<"), to(lambda v: asm("LESS"))),
  seq(BinOp(">"), to(lambda v: asm("GREATER"))),
  seq(BinOp("=="), to(lambda v: asm("EQUAL"))),
  seq(BinOp("~"), to(lambda v: asm("NOT"))),
  seq(ShlConst(X), shl_const),
  seq(ShrConst(X), shr_const),
  seq(Func(X), func),
  seq(Label(X), label),
  seq(Return(X), epilog),
  seq(GotoIf0(X), goto_if_0),
  seq(Goto(X), goto),
  seq(Nop(), to(lambda v: asm("NOP"))),
  seq(Asm(X), to(lambda v: asm(v.X)))
))

Modul macro.py memungkinkan Anda untuk "memperluas" kamus bahasa target dengan menggunakan definisi makro dalam assembler prosesor target. Untuk Java Compiler, definisi di macro.py tidak akan berbeda dari operator "asli" dan fungsi bahasa. Jadi, misalnya, dalam kompiler asli, fungsi I / O dari nilai di port eksternal didefinisikan. Urutan uji operasi dengan memori dan variabel lokal dan operasi penundaan waktu ditambahkan.

@macro(1,0)
def testasm(c,x):
  return Asm("1 1 OUTPORT 0 1 OUTPORT 11 10 STORE 10 FETCH 1 OUTPORT  15 100 STORE 100  FETCH 1 OUTPORT")

@macro(1,0)
def testlocal(c,x):
   return Asm("1 100 STORE 2 101 STORE 100 SETLOCAL LOCAL NOP FETCH 1 OUTPORT LOCAL 1 PLUS NOP FETCH 1 OUTPORT")

@prim(1, 0)
def delay(c, val):
  return [val, Asm("DO LOOP")]


Pengujian


Program tingkat tinggi pengujian kecil untuk prosesor kami berisi definisi fungsi untuk menghitung faktorial, dan fungsi utama yang mengimplementasikan output seri nilai faktorial dari 1 hingga 7 ke port dalam loop tak terbatas.

def fact(n):
  r = 1
  while n > 1:
    r *= n
    n -= 1
  return r


def main():
  n=1
  while True:
     digital_write(1, fact(n))
     delay(10)
     n=(n+1)&0x7


Ini dapat diluncurkan untuk kompilasi, misalnya, dengan skrip sederhana atau dari baris perintah dengan urutan: Akibatnya, file boot stream.bin akan dihasilkan, yang dapat ditransfer ke inti prosesor dalam FPGA melalui port serial (dalam realitas modern, melalui port serial virtual yang disediakan oleh konverter) Antarmuka USB-UART). Program sebagai hasilnya menempati 146 kata (9-bit) dari memori program dan 3 dalam memori data.
c.py C:\D\My_Docs\Documents\uzh-master\tests\fact2.py




Kesimpulan


Secara umum, kompiler Uzh tampaknya menjadi toolkit yang mudah dan nyaman untuk mengembangkan perangkat lunak untuk prosesor perangkat lunak. Ini adalah alternatif yang bagus untuk assembler, setidaknya dalam hal kegunaan programmer. Toolkit untuk mendefinisikan primitif dan makro sebagai fungsi dari bahasa target memungkinkan tempat-tempat penting untuk diimplementasikan dalam bahasa assembly prosesor. Untuk prosesor arsitektur stack, prosedur adaptasi kompiler tidak terlalu rumit dan panjang. Kita dapat mengatakan bahwa ini adalah kasus ketika ketersediaan kode sumber kompiler membantu - bagian utama dari kompiler berubah.

Hasil sintesis prosesor (kapasitas 32-bit, 4K kata memori program dan 1K RAM) untuk FPGA Altera Cyclone V series memberikan yang berikut:

Family	Cyclone V
Device	5CEBA4F23C7
Logic utilization (in ALMs)	694 / 18,480 ( 4 % )
Total registers	447
Total pins	83 / 224 ( 37 % )
Total virtual pins	0
Total block memory bits	72,192 / 3,153,920 ( 2 % )
Total DSP Blocks	2 / 66 ( 3 % )

literatur

  1. Prosesor selanjutnya pada VHDL // m.habr.com/en/post/149686
  2. Python - Wikipedia // en.wikipedia.org/wiki/Python
  3. Kita mulai FPGA dengan Python _ Habr // m.habr.com/en/post/439638
  4. MyHDL // www.myhdl.org
  5. GitHub - true-grue_uzh_ Uzh compiler // github.com/true-grue/uzh
  6. GitHub - true-grue_raddsl_ Alat untuk prototyping cepat kompiler DSL // github.com/true-grue/raddsl
  7. sovietov.com/txt/dsl_python_conf.pdf

Penulis berterima kasih kepada pengembang prosesor perangkat lunak Zmey dan kompiler Uzh untuk konsultasi dan kesabaran.

All Articles