Another simple homemade processor on verilog

 3r???. 3r3-31. 3r3-3579. The article describes the next
bicycle
CPU. 3r33577.  3r???. Instead of the usual RISC /СISC processor, it does not possess a set of instructions as such, only a single copy instruction. 3r33577.  3r???. Similar processors are at Maxim a series MAXQ 3r37373. . 3r38080.
3r33573. 3r33577.  3r???. 3r3-3579. To begin with we will describe ROM, memory of programs 3r38080. 3r33577.  3r???. 3r33540. 3r33535. module rom1r (addr_r, data_r); 3r???. Parameter ADDR_WIDTH = 8; 3r???. Parameter DATA_WIDTH = 8; 3r???. input[ADDR_WIDTH - 1 : 0]addr_r; 3r???. output[DATA_WIDTH - 1 : 0]data_r; 3r???. reg[DATA_WIDTH - 1 : 0]mem[0 : (1ADDR_WIDTH) - 1]; 3r???. initial $ readmemh ("rom.txt", mem, ? (1ADDR_WIDTH) - 1); 3r???. assign data_r = mem[addr_r]; 3r???. endmodule 3r33535. 3r33577.  3r???. 3r3-3579. two-port RAM for data memory 3r3580. 3r33577.  3r???. 3r33540. 3r33535. module ram1r1w (clk_wr, addr_w, data_w, addr_r, data_r); 3r???. Parameter ADDR_WIDTH = 8; 3r???. Parameter DATA_WIDTH = 8; 3r???. input clk_wr; 3r???. input[ADDR_WIDTH - 1 : 0]addr_r, addr_w; 3r???. output[DATA_WIDTH - 1 : 0]data_r; 3r???. input[DATA_WIDTH - 1 : 0]data_w; 3r???. reg[DATA_WIDTH - 1 : 0]mem[0 : (1ADDR_WIDTH) - 1]; 3r???. assign data_r = mem[addr_r]; 3r???. always @ (posedge clk_wr) mem[addr_w] <= data_w;
endmodule 3r33535. 3r33577.  3r???. 3r3-3579. and the processor itself is 3r38080. 3r33577.  3r???. 3r33540. 3r33535. module cpu (clk, reset, port); 3r???. Parameter WIDTH = 8; 3r???. Parameter RAM_SIZE = WIDTH; 3r???. Parameter ROM_SIZE = WIDTH; 3r???. input clk, reset; 3r???. output[WIDTH-1 : 0]port; 3r? 3562. 3r33535. 3r33577.  3r???. 3r3-3579. At a minimum, it needs a register for the instruction counter, as well as one auxiliary register, and also the IO port register, in order to have something to show outside our processor. 3r38080. 3r33577.  3r???. 3r33540. 3r33535. reg[WIDTH-1 : 0]reg_pc; 3r???. reg[WIDTH-1 : 0]reg_reg; 3r???. reg[WIDTH-1 : 0]reg_port; 3r???. assign port = reg_port; 3r? 3562. 3r33535. 3r33577.  3r???. 3r3-3579. The program counter will be the address for the program memory. 3r38080. 3r33577.  3r???. 3r33540. 3r33535. wire[WIDTH-1 : 0]addr_w, addr_r, data_r, data_w, data; 3r???. rom1r rom (reg_pc, {addr_w, addr_r}); 3r???. defparam rom.ADDR_WIDTH = ROM_SIZE; 3r???. defparam rom.DATA_WIDTH = RAM_SIZE * 2; 3r? 3562. 3r33535. 3r33577.  3r???. 3r3-3579. The double-width program memory contains two addresses: where and from where to copy the data in the two-port data memory. 3r38080. 3r33577.  3r???. 3r33540. 3r33535. ram1r1w ram (clk, addr_w, data_w, addr_r, data_r); 3r???. defparam ram.ADDR_WIDTH = RAM_SIZE; 3r???. defparam ram.DATA_WIDTH = WIDTH; 3r? 3562. 3r33535. 3r33577.  3r???. 3r3-3579. We denote special addresses: command counter, constant generator, check for 0 (for conditional jumps), addition /subtraction operations, and input /output port, in this case only output. 3r38080. 3r33577.  3r???. 3r33540. 3r33535. Parameter PC = 0; 3r???. Parameter CG = 1; 3r???. parameter TST = 2; 3r???. parameter ADD = 3; 3r???. Parameter SUB = 4; 3r???. Parameter PORT = 5; 3r? 3562. 3r33535. 3r33577.  3r???. 3r3-3579. The data buses of the two memory ports are not simply interconnected, but through multiplexers, which will at the same time play the role of an ALU. 3r33577.  3r???. One multiplexer is on the read port data bus to read the command counter (for relative transitions), IO, etc., instead of the memory at certain addresses. 3r33577.  3r???. The second is on the write port data bus to not only transfer the data in memory, but also to change them when writing to certain addresses. 3r38080. 3r33577.  3r???. 3r33540. 3r33535. assign data = (addr_r == PC)? reg_pc + 1:
(addr_r == PORT)? reg_port:
data_r; 3r???. 3r???. assign data_w = (addr_w == CG)? addr_r:
(addr_w == TST)? | dаta:
(addr_w == ADD)? data + reg_reg:
(addr_w == SUB)? data - reg_reg:
data; 3r? 3562. 3r33535. 3r33577.  3r???. 3r3-3579. The auxiliary reg_reg register, which is used for arithmetic operations, is not directly accessible, but the result of each instruction is copied into it. 3r33577.  3r???. Thus, to add two values ​​from the memory, one of them must first be read anywhere, for example, copy itself into itself (and at the same time in reg_reg), and the next recording command at the address of the adder will write there the sum with the previous value. 3r33577.  3r???. The constant generator writes to itself the address, not the value of the memory at that address. 3r33577.  3r???. For unconditional jumps, simply copy the desired address to reg_pc, and for conditional jumps, reserve another TST address, which turns any non-zero value into ? and at the same time increases the command counter by 2 instead of 1 to skip the command following it, if the result is not 0. 3r38080. 3r33577.  3r???. 3r33540. 3r33535. 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); 3r???. case (addr_w) 3r33592. PORT: reg_port <= data_w;
endcase 3r???. end
end
end
endmodule 3r33535. 3r33577.  3r???. 3r33636. 3r33537. cpu.v [/b] 3r? 3539. 3r33540. 3r33535. module rom1r (addr_r, data_r); 3r???. Parameter ADDR_WIDTH = 8; 3r???. Parameter DATA_WIDTH = 8; 3r???. input[ADDR_WIDTH - 1 : 0]addr_r; 3r???. output[DATA_WIDTH - 1 : 0]data_r; 3r???. reg[DATA_WIDTH - 1 : 0]mem[0 : (1ADDR_WIDTH) - 1]; 3r???. initial $ readmemh ("rom.txt", mem, ? (1ADDR_WIDTH) - 1); 3r???. assign data_r = mem[addr_r]; 3r???. endmodule
3r???. module ram1r1w (write, addr_w, data_w, addr_r, data_r); 3r???. Parameter ADDR_WIDTH = 8; 3r???. Parameter DATA_WIDTH = 8; 3r???. input write; 3r???. input[ADDR_WIDTH - 1 : 0]addr_r, addr_w; 3r???. output[DATA_WIDTH - 1 : 0]data_r; 3r???. input[DATA_WIDTH - 1 : 0]data_w; 3r???. reg[DATA_WIDTH - 1 : 0]mem[0 : (1ADDR_WIDTH) - 1]; 3r???. assign data_r = mem[addr_r]; 3r???. always @ (posedge write) mem[addr_w] <= data_w;
endmodule
3r???. module cpu (clk, reset, port); 3r???. Parameter WIDTH = 8; 3r???. Parameter RAM_SIZE = 8; 3r???. Parameter ROM_SIZE = 8; 3r???. 3r???. Parameter PC = 0; 3r???. Parameter CG = 1; 3r???. parameter TST = 2; 3r???. parameter ADD = 3; 3r???. Parameter SUB = 4; 3r???. Parameter PORT = 5; 3r???. 3r???. input clk, reset; 3r???. output[WIDTH-1 : 0]port; 3r???. 3r???. wire[WIDTH-1 : 0]addr_r, addr_w, data_r, data_w, data; 3r???. 3r???. reg[WIDTH-1 : 0]reg_pc; 3r???. reg[WIDTH-1 : 0]reg_reg; 3r???. reg[WIDTH-1 : 0]reg_port; 3r???. assign port = reg_port; 3r???. 3r???. rom1r rom (reg_pc, {addr_w, addr_r}); 3r???. defparam rom.ADDR_WIDTH = ROM_SIZE; 3r???. defparam rom.DATA_WIDTH = RAM_SIZE * 2; 3r???. 3r???. ram1r1w ram (clk, addr_w, data_w, addr_r, data_r); 3r???. defparam ram.ADDR_WIDTH = RAM_SIZE; 3r???. defparam ram.DATA_WIDTH = WIDTH; 3r???. 3r???. assign data = (addr_r == PC)? reg_pc + 1:
(addr_r == PORT)? reg_port:
data_r; 3r???. 3r???. assign data_w = (addr_w == CG)? addr_r:
(addr_w == TST)? | dаta:
(addr_w == ADD)? data + reg_reg:
(addr_w == SUB)? data - reg_reg:
data; 3r???. 3r???. 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); 3r???. case (addr_w) 3r33592. PORT: reg_port <= data_w;
endcase 3r???. end
end
end
endmodule 3r33535. 3r33588. 3r33588. 3r33577.  3r???. 3r3-3579. That's actually the whole processor. 3r38080. 3r33577.  3r???.
Assembler 3r33232. 3r33577.  3r???. 3r3-3579. Now we will write a simple program for it, which simply gives the values ​​to the port in sequence, and stops at 5.
 3r???. Writing the assembler itself, even so simple (all the syntax is A = B), was lazy, so instead the basis was taken ready language Lua, which is very well suited for building various Domain Specific Language based on it, at the same time we get a ready Lua preprocessor . 3r33577.  3r???. First, the declaration of special addresses, the entry in which changes the data and variable of the counter at 7
3r33577.  3r???. 3r33540. 3r33471. require ("asm")
3r???. PC = mem (0) 3r39292. CG = mem (1) 3r39292. TST = mem (2) 3r39292. ADD = mem (3) 3r33592. SUB = mem (4) 3r33592. PORT = mem (5) 3r3r9292. 3r???. cnt = mem (7) 3r33562. 3r33535. 3r33577.  3r???. 3r3-3579. Instead of macros, you can use the usual Lua functions, though due to the fact that the _G GG of the environment was changed to catch assignments (see below), global variables also fell off at the same time: declaring a non-local variable some_variable = 0xAA our assembler will consider it “its” and try to parse it, instead, for declarations of the preprocessor global variable, you will have to use rawset (_G, some_variable, 0xAA), which does not touch the metamethods. 3r38080. 3r33577.  3r???. 3r33540. 3r33471. function jmp (l) 3r3r9292. CG = l 3r39292. PC = CG 3r???. end 3r33535. 3r33577.  3r???. 3r3-3579. Labels will be denoted by the word label and string constants; in Lua, in the case of a single string argument, the function of the bracket can be omitted. 3r38080. 3r33577.  3r???. 3r33540. 3r33471. label "start" 3r33535. 3r33577.  3r???. 3r3-3579. Reset the port counter and register: 3r38080. 3r33577.  3r???. 3r33540. 3r33471. CG = 0 r3r3592. cnt = CG
PORT = CG 3r33535. 3r33577.  3r???. 3r3-3579. In the loop, load the constant ? add it to the counter variable and show it to the port:
3r33577.  3r???. 3r33540. 3r33471. label "loop"
CG = 1 3r39292. ADD = cnt - add = cnt + 1
cnt = add 3r3r9292. PORT = ADD 3r33535. 3r33577.  3r???. 3r3-3579. Add the missing before overflow to 0 and, if there is not zero, go to the beginning, skipping CG = "exit", otherwise we end up in an infinite "exit" cycle. 3r38080. 3r33577.  3r???. 3r33540. 3r33471. CG = -5 r3r3592. ADD = ADD --add = add + 251
CG = "loop"
TST = ADD --skip "exit" if not 0
CG = "exit"
PC = CG 3r???. 3r???. label "exit"
jmp "exit" 3r33535. 3r33577.  3r???. 3r33636. 3r33537. test.lua [/b] 3r? 3539. 3r33540. 3r33471. require ("asm")
3r???. PC = mem (0) 3r39292. CG = mem (1) 3r39292. TST = mem (2) 3r39292. ADD = mem (3) 3r33592. SUB = mem (4) 3r33592. PORT = mem (5) 3r3r9292. 3r???. cnt = mem (7) 3r33592. 3r???. function jmp (l) 3r3r9292. CG = l 3r39292. PC = CG 3r???. end
3r???. label "start"
CG = 0 r3r3592. cnt = CG
PORT = CG
3r???. label "loop"
CG = 1 3r39292. ADD = cnt - add = cnt + 1
cnt = add 3r3r9292. PORT = ADD
3r???. CG = -5 r3r3592. ADD = ADD --add = add + 256 - 5
CG = "loop"
TST = ADD --skip "exit" if not 0
CG = "exit"
PC = CG 3r???. 3r???. label "exit"
jmp "exit" 3r33535. 3r33588. 3r33588. 3r33577.  3r???. 3r3-3579. And now actually the asm.lua assembler itself, as it should be in 20 lines: 3r38080. 3r33577.  3r???. 3r3-3579. In the mem function (for the declaration of special addresses), it would be necessary to add an automatic assignment of the next free address, if it is not specified as an argument. 3r33577.  3r???. And for tags, you should check for redeclaration of the existing 3r38080 tag. 3r33577.  3r???. 3r33540. 3r33471. local output = {}
local labels = {}
function mem (addr) return addr end
function label (name) labels[name]= #output end 3r33535. 3r33577.  3r???. 3r3-3579. In Lua, there is no metamethod for assignment, but there are metamethods for indexing existing values ​​and for adding new ones, including for the _G global environment table. 3r33577.  3r???. Since __newindex works only for values ​​that do not exist in the table, instead of adding new elements to _G, you need to hide them somewhere, without adding them to _G, and, accordingly, get them out when they are addressed through __index. 3r33577.  3r???. If the name already exists, then add this instruction to the others. 3r38080. 3r33577.  3r???. 3r33540. 3r33471. 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}) r3r3592. else g[k]= v end
end
}) 3r36262. 3r33535. 3r33577.  3r???. 3r3-3579. Well, after the execution of the assembler program, when the garbage collector finally comes for an array with our output program, we simply print it, at the same time replacing text labels with the correct addresses. 3r38080. 3r33577.  3r???. 3r33540. 3r33471. setmetatable (output, {
__gc = function (o) 3r39292. 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 3rr3292.)) 3r332352. . 3r33535. 3r33577.  3r???. 3r33636. 3r33537. asm.lua [/b] 3r? 3539. 3r33540. 3r33471. local output = {}
local labels = {}
3r???. function mem (addr) return addr end
function label (name) labels[name]= #output end
3r???. 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}) r3r3592. else g[k]= v end
end
}) 3r???. 3r???. setmetatable (output, {
__gc = function (o) 3r39292. 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 3r3-39592.}) 3r33562. 3r33535. 3r33588. 3r33588. 3r33577.  3r???. 3r3-3579. By running lua53 test.lua> rom.txt (
Or online
) We get a program for the processor in machine codes. 3r38080. 3r33577.  3r???. 3r33636. 3r33537. rom.txt [/b] 3r? 3539. 3r33540. 3r33511. 0100
0701 3r39292. 0501 3r39292. 0101 3r39292. 0307 3r39292. 0703 3r???. 0503 3r???. 01FB
0303 3r39292. 0103 3r???. 0203 3r?392. 010D
0001 3r39292. 010D
0001 3r36262. 3r33535. 3r33588. 3r33588. 3r33577.  3r???. 3r3-3579. For the simulation, let's make a simple testbench that only releases the reset and pulls the clocks. 3r38080. 3r33577.  3r???. 3r33636. 3r33537. test.v 3r33538. 3r? 3539. 3r33540. 3r33535. `include" cpu.v "
3r???. module test (); 3r???. reg clk; 3r???. reg reset; 3r???. wire[7:0]port; 3r???. 3r???. cpu c (clk, reset, port); 3r???. 3r???. initial
begin
$ dumpfile ("test.vcd"); 3r???. reset <= 1;
clk 3r33556. # 4 reset <= 0;
# 150 $ finish; 3r???. end
3r???. always # 1 clk <= !clk;
3r???. endmodule 3r33535. 3r33588. 3r33588. 3r33577.  3r???. 3r3-3579. Having stimulated with the help of iverilog -o test.vvp test.v, open the resulting test.vcd in GTKWave:
 3r???. 3r33571. 3r? 3572. 3r33573. 3r33577.  3r???. the port counts to five, and then the processor loops. 3r38080. 3r33577.  3r???. 3r3-3579. Now, when there is a minimal working processor, you can add other arithmetic, logical operations, multiplication, division, floating point, trigonometry, registers for indirect access to memory, stacks, hardware cycles, various peripherals, as needed, and start sawing backend for llvm. 3r38080. 3r33588. 3r???. 3r???. 3r???. 3r33585. ! function (e) {function t (t, n) {if (! (n in e)) {for (var r, a = e.document, i = a.scripts, o = i.length; o-- ;) if (-1! == i[o].src.indexOf (t)) {r = i[o]; break} if (! r) {r = a.createElement ("script"), r.type = "text /jаvascript", r.async =! ? r.defer =! ? r.src = t, r.charset = "UTF-8"; var d = function () {var e = a.getElementsByTagName ("script")[0]; e.parentNode.insertBefore (r, e)}; "[object Opera]" == e.opera? a.addEventListener? a.addEventListener ("DOMContentLoaded", d,! 1): e.attachEvent ("onload", d ): d ()}}} t ("//mediator.mail.ru/script/2820404/"""_mediator") () (); 3r33586. 3r???. 3r33588. 3r???. 3r???. 3r???. 3r???.
FPGA / Lua
+ 0 -

Add comment