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
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
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
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))
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.