Prosesor Verilog sederhana lainnya

Artikel ini menjelaskan prosesor dan assembler primitif berikutnya untuknya.
Alih-alih RISC / CISC biasa, prosesor tidak memiliki satu set instruksi per se, hanya ada satu salinan instruksi.


Prosesor serupa memiliki seri Maxim MAXQ .


Pertama, mari kita gambarkan ROM, memori program


module rom1r(addr_r, data_r); parameter ADDR_WIDTH = 8; parameter DATA_WIDTH = 8; input [ADDR_WIDTH - 1 : 0] addr_r; output [DATA_WIDTH - 1 : 0] data_r; reg [DATA_WIDTH - 1 : 0] mem [0 : (1<<ADDR_WIDTH) - 1]; initial $readmemh("rom.txt", mem, 0, (1<<ADDR_WIDTH) - 1); assign data_r = mem[addr_r]; endmodule 

RAM port ganda untuk memori data


 module ram1r1w(clk_wr, addr_w, data_w, addr_r, data_r); parameter ADDR_WIDTH = 8; parameter DATA_WIDTH = 8; input clk_wr; input [ADDR_WIDTH - 1 : 0] addr_r, addr_w; output [DATA_WIDTH - 1 : 0] data_r; input [DATA_WIDTH - 1 : 0] data_w; reg [DATA_WIDTH - 1 : 0] mem [0 : (1<<ADDR_WIDTH) - 1]; assign data_r = mem[addr_r]; always @ (posedge clk_wr) mem[addr_w] <= data_w; endmodule 

dan prosesor itu sendiri


 module cpu(clk, reset, port); parameter WIDTH = 8; parameter RAM_SIZE = WIDTH; parameter ROM_SIZE = WIDTH; input clk, reset; output [WIDTH-1 : 0] port; 

Minimal, ia membutuhkan register dari konter perintah, serta satu register tambahan, dan port register IO, sehingga ada sesuatu untuk ditampilkan dari prosesor kami.


  reg [WIDTH-1 : 0] reg_pc; reg [WIDTH-1 : 0] reg_reg; reg [WIDTH-1 : 0] reg_port; assign port = reg_port; 

Penghitung perintah akan menjadi alamat untuk memori program.


  wire [WIDTH-1 : 0] addr_w, addr_r, data_r, data_w, data; rom1r rom (reg_pc, {addr_w, addr_r}); defparam rom.ADDR_WIDTH = ROM_SIZE; defparam rom.DATA_WIDTH = RAM_SIZE * 2; 

Memori program dengan lebar dua kali lipat berisi dua alamat: di mana dan dari mana menyalin data ke memori data dual-port.


  ram1r1w ram (clk, addr_w, data_w, addr_r, data_r); defparam ram.ADDR_WIDTH = RAM_SIZE; defparam ram.DATA_WIDTH = WIDTH; 

Kami akan menunjuk alamat khusus: penghitung perintah, generator konstan, cek 0 (untuk lompatan bersyarat), operasi penjumlahan / pengurangan, dan port input-output, dalam hal ini, hanya output.


  parameter PC = 0; parameter CG = 1; parameter TST = 2; parameter ADD = 3; parameter SUB = 4; parameter PORT = 5; 

Bus data dari dua port memori tidak hanya saling terhubung, tetapi melalui multiplexer, yang pada saat yang sama akan berfungsi sebagai ALU.


Satu multiplexer - pada bus data port baca sehingga alih-alih memori pada alamat tertentu, baca counter perintah (untuk transisi relatif), IO, dll.


Yang kedua adalah pada bus data dari port rekaman, sehingga tidak hanya untuk mentransfer data dalam memori, tetapi juga ketika mengubah alamat tertentu untuk mengubahnya.


  assign data = (addr_r == PC) ? reg_pc : (addr_r == PORT) ? reg_port : data_r; assign data_w = (addr_w == CG) ? addr_r : (addr_w == TST) ? |data : (addr_w == ADD) ? data + reg_reg : (addr_w == SUB) ? data - reg_reg : data; 

Register bantu reg_reg, yang digunakan untuk operasi aritmatika, tidak dapat diakses secara langsung, tetapi hasil dari setiap instruksi disalin ke sana.


Jadi, untuk menambahkan dua nilai dari memori, Anda harus terlebih dahulu membaca salah satunya di mana saja, misalnya, menyalinnya sendiri (dan pada saat yang sama di reg_reg), dan perintah tulis berikutnya di alamat penambah akan mencatat jumlah dengan nilai sebelumnya di sana.


Generator konstan menulis alamat, bukan nilai memori, ke alamat ini.


Untuk lompatan tanpa syarat, Anda hanya perlu menyalin alamat yang ingin Anda reg_pc, dan untuk lompatan bersyarat, cadangan alamat TST lainnya, yang mengubah nilai bukan nol menjadi 1, dan pada saat yang sama menambahkan penghitung perintah dengan 2 alih-alih 1 untuk melewati perintah berikutnya jika hasilnya bukan 0.


  always @ (posedge clk) begin if (reset) begin reg_pc <= 0; end else begin reg_reg <= data_w; if (addr_w == PC) begin reg_pc <= data_w; end else begin reg_pc <= reg_pc + (((addr_w == TST) && data_w[0]) ? 2 : 1); case (addr_w) PORT: reg_port <= data_w; endcase end end end endmodule 

cpu.v
 module rom1r(addr_r, data_r); parameter ADDR_WIDTH = 8; parameter DATA_WIDTH = 8; input [ADDR_WIDTH - 1 : 0] addr_r; output [DATA_WIDTH - 1 : 0] data_r; reg [DATA_WIDTH - 1 : 0] mem [0 : (1<<ADDR_WIDTH) - 1]; initial $readmemh("rom.txt", mem, 0, (1<<ADDR_WIDTH) - 1); assign data_r = mem[addr_r]; endmodule module ram1r1w(write, addr_w, data_w, addr_r, data_r); parameter ADDR_WIDTH = 8; parameter DATA_WIDTH = 8; input write; input [ADDR_WIDTH - 1 : 0] addr_r, addr_w; output [DATA_WIDTH - 1 : 0] data_r; input [DATA_WIDTH - 1 : 0] data_w; reg [DATA_WIDTH - 1 : 0] mem [0 : (1<<ADDR_WIDTH) - 1]; assign data_r = mem[addr_r]; always @ (posedge write) mem[addr_w] <= data_w; endmodule module cpu(clk, reset, port); parameter WIDTH = 8; parameter RAM_SIZE = 8; parameter ROM_SIZE = 8; parameter PC = 0; parameter CG = 1; parameter TST = 2; parameter ADD = 3; parameter SUB = 4; parameter PORT = 5; input clk, reset; output [WIDTH-1 : 0] port; wire [WIDTH-1 : 0] addr_r, addr_w, data_r, data_w, data; reg [WIDTH-1 : 0] reg_pc; reg [WIDTH-1 : 0] reg_reg; reg [WIDTH-1 : 0] reg_port; assign port = reg_port; rom1r rom(reg_pc, {addr_w, addr_r}); defparam rom.ADDR_WIDTH = ROM_SIZE; defparam rom.DATA_WIDTH = RAM_SIZE * 2; ram1r1w ram (clk, addr_w, data_w, addr_r, data_r); defparam ram.ADDR_WIDTH = RAM_SIZE; defparam ram.DATA_WIDTH = WIDTH; assign data = (addr_r == PC) ? reg_pc : (addr_r == PORT) ? reg_port : data_r; assign data_w = (addr_w == CG) ? addr_r : (addr_w == TST) ? |data : (addr_w == ADD) ? data + reg_reg : (addr_w == SUB) ? data - reg_reg : data; always @ (posedge clk) begin if (reset) begin reg_pc <= 0; end else begin reg_reg <= data_w; if (addr_w == PC) begin reg_pc <= data_w; end else begin reg_pc <= reg_pc + (((addr_w == TST) && data_w[0]) ? 2 : 1); case (addr_w) PORT: reg_port <= data_w; endcase end end end endmodule 

Itu keseluruhan prosesor.


Assembler


Sekarang kita akan menulis untuknya sebuah program sederhana yang hanya menampilkan nilai-nilai ke port secara berurutan dan berhenti pada angka 5.


Itu terlalu malas untuk menulis assembler sendiri, bahkan yang sederhana (seluruh sintaksis A = B), jadi alih-alih, bahasa Lua yang sudah jadi digunakan sebagai dasar, yang sangat cocok untuk membangun berbagai Bahasa Domain Tertentu berdasarkan padanya, pada saat yang sama kami mendapatkan preprosesor Lua yang sudah jadi secara gratis .


Pertama, pengumuman alamat khusus, entri yang mengubah data dan variabel counter di alamat 7


 require ("asm") PC = mem(0) CG = mem(1) TST = mem(2) ADD = mem(3) SUB = mem(4) PORT = mem(5) cnt = mem(7) 

Alih-alih makro, Anda dapat menggunakan fungsi Lua biasa, meskipun karena fakta bahwa metatable lingkungan _G diubah untuk menangkap tugas (lihat di bawah), variabel global jatuh pada saat yang sama: deklarasi variabel nonlokal some_variable = 0xAA assembler kami anggap miliknya sendiri dan mencoba mengurai sebagai gantinya, untuk mendeklarasikan variabel preprocessor global, Anda harus menggunakan rawset (_G, some_variable, 0xAA), yang tidak menyentuh metametode.


 function jmp(l) CG = l PC = CG end 

Label akan dilambangkan dengan label kata dan konstanta string, dalam Lua, dalam kasus argumen string tunggal untuk fungsi, tanda kurung dapat dihilangkan.


 label "start" 

Kosongkan penghitung port dan daftarkan:


 CG = 0 cnt = CG PORT = CG 

Dalam loop, muat konstanta 1, tambahkan ke variabel counter dan tunjukkan di port:


 label "loop" CG = 1 ADD = cnt -- add = cnt + 1 cnt = ADD PORT = ADD 

Tambahkan yang hilang ke overflow pada 0 dan, jika tidak ada nol, pergi ke awal, lewati CG = "keluar", kalau tidak kita berakhir dengan loop keluar "keluar".


 CG = -5 ADD = ADD --add = add + 251 CG = "loop" TST = ADD --skip "exit" if not 0 CG = "exit" PC = CG label "exit" jmp "exit" 

test.lua
 require ("asm") PC = mem(0) CG = mem(1) TST = mem(2) ADD = mem(3) SUB = mem(4) PORT = mem(5) cnt = mem(7) function jmp(l) CG = l PC = CG end label "start" CG = 0 cnt = CG PORT = CG label "loop" CG = 1 ADD = cnt -- add = cnt + 1 cnt = ADD PORT = ADD CG = -5 ADD = ADD --add = add + 256 - 5 CG = "loop" TST = ADD --skip "exit" if not 0 CG = "exit" PC = CG label "exit" jmp "exit" 

Dan sekarang assembler asm.lua itu sendiri, sebagaimana mestinya dalam 20 baris:


Dalam fungsi mem (untuk mendeklarasikan alamat khusus), orang juga akan menambahkan penugasan otomatis dari alamat gratis berikutnya, jika seseorang tidak ditentukan sebagai argumen.
Dan untuk tag, Anda perlu memeriksa untuk mendeklarasikan ulang tag yang ada


 local output = {} local labels = {} function mem(addr) return addr end function label(name) labels[name] = #output end 

Lua tidak memiliki metamethod untuk tugas, tetapi ada metamethod untuk mengindeks nilai yang ada dan untuk menambahkan yang baru, termasuk tabel lingkungan global _G.
Karena __newindex hanya berfungsi untuk nilai yang tidak ada dalam tabel, alih-alih menambahkan elemen baru ke _G, Anda perlu menyembunyikannya di suatu tempat tanpa menambahkan _G, dan, dengan demikian, mengeluarkannya saat diakses melalui __index.


Jika nama sudah ada, maka tambahkan instruksi ini ke yang lainnya.


 local g = {} setmetatable(_G, { __index = function(t, k, v) return g[k] end, __newindex = function(t, k, v) if g[k] then table.insert(output, {g[k], v}) else g[k]=v end end }) 

Nah, setelah menjalankan program assembler, ketika pengumpul sampah akhirnya datang untuk array dengan program output kami, kami hanya mencetaknya, pada saat yang sama mengganti label teks dengan alamat yang benar.


 setmetatable(output, { __gc = function(o) for i,v in ipairs(o) do if type(v[2]) == "string" then v[2] = labels[v[2]] or print("error: ", v[2]) end print(string.format("%02X%02X", v[1] & 0xFF, v[2] & 0xFF)) end end }) 

asm.lua
 local output = {} local labels = {} function mem(addr) return addr end function label(name) labels[name] = #output end local g = {} setmetatable(_G, { __index = function(t, k, v) return g[k] end, __newindex = function(t, k, v) if g[k] then table.insert(output, {g[k], v}) else g[k]=v end end }) setmetatable(output, { __gc = function(o) for i,v in ipairs(o) do if type(v[2]) == "string" then v[2] = labels[v[2]] or print("error: ", v[2]) end print(string.format("%02X%02X", v[1] & 0xFF, v[2] & 0xFF)) --FIX for WIDTH > 8 end end }) 

Dengan menjalankan lua53 test.lua> rom.txt ( atau online ) kami mendapatkan program untuk prosesor dalam kode mesin.


rom.txt
 0100 0701 0501 0101 0307 0703 0503 01FB 0303 0103 0203 010D 0001 010D 0001 

Untuk mensimulasikan, kita akan membuat testbench sederhana yang hanya melepaskan reset dan menarik cabikan.


test.v
 `include "cpu.v" module test(); reg clk; reg reset; wire [7:0] port; cpu c(clk, reset, port); initial begin $dumpfile("test.vcd"); reset <= 1; clk <= 0; #4 reset <= 0; #150 $finish; end always #1 clk <= !clk; endmodule 

Setelah disimulasikan menggunakan iverilog -o test.vvp test.v, buka test.vcd yang dihasilkan di GTKWave:

port dihitung sampai lima, dan kemudian loop prosesor.


Sekarang ada prosesor yang berfungsi minimal, Anda dapat menambahkan sisa aritmatika, operasi logis, perkalian, pembagian, floating point, trigonometri, register untuk akses memori tidak langsung, tumpukan, siklus perangkat keras, berbagai periferal, sesuai kebutuhan, ... dan mulai menggergaji backend untuk llvm.

Source: https://habr.com/ru/post/id433342/


All Articles