实例十三 Natalius 8位RISC处理器

6.2 实例十三 Natalius 8位RISC处理器

6.2.1. 本章导读

设计目的
了解并熟悉Natalius 8位RISC处理器的基本结构和运行原理。根据Natalius的指令集设计出可以验证一些简单功能的testbench,最后通过Robei可视化仿真软件进行功能实现和仿真验证(由于Robei目前暂不支持$readmemh()命令,最后的仿真验证在Modelsim中进行)。
设计原理
1. Natalius简介
Natalius是一个结构紧凑、多功能且完全嵌入式的完全以Verilog设计的8位RISC处理器内核。Natalius提供了一个可以运行在python控制台上的汇编器。Instruction memory中存储了2048条指令,每条指令16位宽,其执行需要运行3个时钟周期。
2. 指令集
Natalius包含了大部分处理器所具有的经典指令集。包括:存储访问、数学运算、逻辑运算和数据流控制。如下表6-2-1和表6-2-2所示。
实例十三 Natalius 8位RISC处理器_第1张图片
实例十三 Natalius 8位RISC处理器_第2张图片
3. Natalius接口信号
Natalius处理器顶层的接口信号如下图6-2-1所示,每个信号的具体含义列在表6-2-3中
实例十三 Natalius 8位RISC处理器_第3张图片
4. 汇编器脚本使用
安装Python:
(1)下载Python
打开浏览器,输入地址:https://www.python.org/downloads/release/python-343/拖动到页面最下方,根据自己的电脑配置选择相应的版本,如图6-2-2所示。
实例十三 Natalius 8位RISC处理器_第4张图片
由于操作的电脑是Windows7 64位操作系统,所以选择Windows x86-64 MSI installer进行下载。
(2)安装Python
双击下载下来的安装包进行安装。并一定要记清安装时选择的目录。默认的安装目录是“C:\python34”。
使用assembler.py对code.asm进行转换:
(1)编写code.asm代码
根据“Natalius 8 bit RISC Processor.pdf”文档中第3页Table 3,并参照第4页到第5页的5.1 Example编写一段测试代码。
编写的一段简单的测试代码如下:
ldi r1, 22
ldi r2, 80
ldi r3, 36
ldi r4, 45
add r1, r2
stm r1, 11
add r3, r4
stm r3, 25
add r1, r3
stm r1, 32
其实现的基本功能是两个数的相加。将测试代码保存为“test.asm”。然后将测试代码“test.asm”和从OpenCores“Natalius 8 bit RISC Processor”下载下来的“assembler.py”一起放在文件夹中,本教程中选择放置于“E:\python_test”中。
(2)在CMD环境中使用Python将code.asm转换为instruction.mem
打开“开始”菜单,在“搜索程序和文件”搜索框中输入“cmd”并按下回车键,进入如下图6-2-3所示的界面:
实例十三 Natalius 8位RISC处理器_第5张图片
a.设定Python路径
如果安装Python的时候没有更改路径,输入以下命令设定Python路径:
set path=%path%;C:\python34
b.打开“test.asm”所在文件夹
本教程中“test.asm”放置于“E:\python_test”,故输入以下命令:
“E:”回车
“cd python_test”回车
然后可以输入dir 或者 dir /b 查看目录中的内容。
实例十三 Natalius 8位RISC处理器_第6张图片
c.输入命令,进行转换
然后输入“assembler.py -s test.asm”,转换完成之后可以看到“E:\python_test”下多出了一个“instructions.mem”文件。该文件即是我们进行仿真时需要用到的文件。

6.2.2. 设计流程

1. ALU模型设计
(1)新建一个模型命名为ALU,类型为module,同时具备4输入3输出,每个引脚的属性和名称参照下图6-2-5进行对应的修改。
实例十三 Natalius 8位RISC处理器_第7张图片
实例十三 Natalius 8位RISC处理器_第8张图片
(2)添加代码。点击模型下方的 Code添加代码。
reg [7:0] resu;
wire [7:0] result;
always@(a or b)
case (opalu)
0: resu <= ~a;
1: resu <= a & b;
2: resu <= a ^ b;
3: resu <= a | b;
4: resu <= a;
5: resu <= a + b;
6: resu <= a - b;
default: resu <= a + 1;
endcase

assign zero=(resu==0);
assign result=resu;
assign carry=(a

always@(result)
case (sh)
0: dshift <= {result[6:0], “0”};
1: dshift <= {result[6:0], result[7]};
2: dshift <= {“0”, result[7:1]};
3: dshift <= {result[0], result[7:1]};
4: dshift <= result;
5: dshift <= {result[6:0], “1”};
6: dshift <= {“1”, result[7:1]};
default: dshift <= result;
endcase

(3)保存模型(存储文件夹路径不能有空格和中文),运行并检查有无错误输出。
2. stack模型设计
(1)新建一个模型命名为stack,类型为module,同时具备7输入2输出,每个引脚的属性和名称参照下图6-2-7进行对应的修改。
实例十三 Natalius 8位RISC处理器_第9张图片
实例十三 Natalius 8位RISC处理器_第10张图片
(2)添加代码。点击模型下方的 Code添加代码。
reg [7:0] resu;
wire [7:0] result;
reg [3:0] addr;
reg [10:0] ram [15:0];
wire [10:0] dout;
wire [10:0] din;

always@(posedge clk or posedge rst)
begin
if (rst)
PC<=0;
else
if (ldpc)
if(selpc)
PC<=ninst_addr;
else
PC<=PC+1;
end

assign din = PC;

always@(posedge clk)
begin
if (rst)
addr<=0;
else
begin
if (wr_en0 && rd_en1)
if (addr>0)
addr<=addr-1;
if (wr_en1 && rd_en0)
if (addr<15)
addr<=addr+1;
end
end

always @(posedge clk)
if (wr_en)
ram[addr] <= din;

assign dout = ram[addr];
assign out = dout + 1;

(3)保存模型(存储文件夹路径不能有空格和中文),运行并检查有无错误输出。
3. data_supply模型设计
(1)新建一个模型命名为data_supply,类型为module,同时具备12输入2输出,每个引脚的属性和名称参照下图6-2-9进行对应的修改。
实例十三 Natalius 8位RISC处理器_第11张图片
实例十三 Natalius 8位RISC处理器_第12张图片
(2)添加代码。点击模型下方的 Code添加代码。
wire [7:0] portB;
wire [7:0] regmux, muxkte;
reg [7:0] mem [7:0];

always@(posedge clk)
begin
mem[0]<=0;
if(we)
mem[wa]<=regmux;
end

assign portA=mem[raa];
assign portB=mem[rab];
assign regmux=insel? shiftout : muxkte;
assign muxkte=selk? kte : data_in;
assign muximm=selimm? imm : portB;

(3)保存模型(存储文件夹路径不能有空格和中文),运行并检查有无错误输出。
4. zc_control模型设计
(1)新建一个模型命名为zc_control,类型为module,同时具备5输入2输出,每个引脚的属性和名称参照下图6-2-11进行对应的修改。
实例十三 Natalius 8位RISC处理器_第13张图片
实例十三 Natalius 8位RISC处理器_第14张图片
(2)添加代码。点击模型下方的 Code添加代码。代码:
always @ (posedge clk or posedge rst)
begin
if (rst)
begin
z<=0;
c<=0;
end
else
if (ldflag)
begin
z<=zero;
c<=carry;
end
end

(3)保存模型(存储文件夹路径不能有空格和中文),运行并检查有无错误输出。
5. data path模型设计
(1)新建一个模型命名为data_path,类型为module,同时具备20输入5输出,每个引脚的属性和名称参照下图6-2-13进行对应的修改。
实例十三 Natalius 8位RISC处理器_第15张图片
实例十三 Natalius 8位RISC处理器_第16张图片
注意:
(1)data_supply的muximm连接到ALU的b端口;ALU的dshift同时连接到data_supply的shiftout和data_path的data_out。
(2)添加代码。由于该模块仅仅是把ALU,stack,data supply和zc control四个模块整合连接起来,所以并无代码。
(3)保存模型(存储文件夹路径不能有空格和中文),运行并检查有无错误输出。
6. instruction memory 模型设计
(1)新建一个模型命名为instruction_memory,类型为module,同时具备2输入1输出,每个引脚的属性和名称参照下图6-2-15进行对应的修改。
实例十三 Natalius 8位RISC处理器_第17张图片
(2)添加代码。点击模型下方的 Code添加代码。
代码:由于Robei软件目前尚不支持 r e a d m e m h ( ) 函 数 , 故 代 码 中 有 两 行 被 注 释 掉 了 。 后 面 使 用 M o d e l s i m 进 行 仿 真 的 时 候 需 要 将 这 两 行 代 码 还 原 。 r e g [ 15 : 0 ] r o m [ 2047 : 0 ] ; w i r e w e ; / / i n i t i a l / / readmemh()函数,故代码中有两行被注释掉了。后面使用Modelsim进行仿真的时候需要将这两行代码还原。 reg [15:0] rom [2047:0]; wire we; //initial // readmemh()使Modelsim仿reg[15:0]rom[2047:0];wirewe;//initial//readmemh(“instructions.mem”, rom, 0, 2047);
assign we=0;

always @(posedge clk)
if(we)
rom[address]<=0;
else
instruction <= rom[address];

(3)保存模型(存储文件夹路径不能有空格和中文),运行并检查有无错误输出。
7. control unit模型设计
(1)新建一个模型命名为control_unit,类型为module,同时具备6输入20输出,每个引脚的属性和名称参照下图进行对应的修改。
实例十三 Natalius 8位RISC处理器_第18张图片
实例十三 Natalius 8位RISC处理器_第19张图片
(2)添加代码。点击模型下方的Code添加代码。
parameter fetch=5’d0;
parameter decode=5’d1;
parameter ldi=5’d2;
parameter ldm=5’d3;
parameter stm=5’d4;
parameter cmp=5’d5;
parameter add=5’d6;
parameter sub=5’d7;
parameter andi=5’d8;
parameter oor=5’d9;
parameter xori=5’d10;
parameter jmp=5’d11;
parameter jpz=5’d12;
parameter jnz=5’d13;
parameter jpc=5’d14;
parameter jnc=5’d15;
parameter csr=5’d16;
parameter ret=5’d17;
parameter adi=5’d18;
parameter csz=5’d19;
parameter cnz=5’d20;
parameter csc=5’d21;
parameter cnc=5’d22;
parameter sl0= 5’d23;
parameter sl1= 5’d24;
parameter sr0=5’d25;
parameter sr1=5’d26;
parameter rrl=5’d27;
parameter rrr=5’d28;
parameter noti=5’d29;
parameter nop=5’d30;

wire [4:0] opcode;
reg [4:0] state;
assign opcode=instruction[15:11];

always@(posedge clk or posedge rst)
begin
if (rst)
state<=decode;
else
case (state)
fetch: state<=decode;
decode:
case (opcode)
2: state<=ldi;
3: state<=ldm;
4: state<=stm;
5: state<=cmp;
6: state<=add;
7: state<=sub;
8: state<=andi;
9: state<=oor;
10: state<=xori;
11: state<=jmp;
12: state<=jpz;
13: state<=jnz;
14: state<=jpc;
15: state<=jnc;
16: state<=csr;
17: state<=ret;
18: state<=adi;
19: state<=csz;
20: state<=cnz;
21: state<=csc;
22: state<=cnc;
23: state<=sl0;
24: state<=sl1;
25: state<=sr0;
26: state<=sr1;
27: state<=rrl;
28: state<=rrr;
29: state<=noti;
default: state<=nop;
endcase
ldi:state<=fetch;
ldm:state<=fetch;
stm:state<=fetch;
cmp:state<=fetch;
add:state<=fetch;
sub:state<=fetch;
andi:state<=fetch;
oor:state<=fetch;
xori:state<=fetch;
jmp:state<=fetch;
jpz:state<=fetch;
jnz:state<=fetch;
jpc:state<=fetch;
jnc:state<=fetch;
csr:state<=fetch;
ret:state<=fetch;
adi:state<=fetch;
csz:state<=fetch;
cnz:state<=fetch;
csc:state<=fetch;
cnc:state<=fetch;
sl0:state<=fetch;
sl1:state<=fetch;
sr0:state<=fetch;
sr1:state<=fetch;
rrl:state<=fetch;
rrr:state<=fetch;
noti:state<=fetch;
nop:state<=fetch;
endcase
end

always @ (state)
begin
port_addr<=0;
write_e<=0;
read_e<=0;
insel<=0;
we<=0;
raa<=0;
rab<=0;
wa<=0;
opalu<=4;
sh<=4;
selpc<=0;
ldpc<=1;
ldflag<=0;
naddress<=0;
selk<=0;
KTE<=0;
wr_en<=0;
rd_en<=0;
imm<=0;
selimm<=0;
case (state)
fetch: ldpc<=0;
decode:
begin
ldpc<=0;
if (opcodestm)
begin
raa<=instruction[10:8];
port_addr<=instruction[7:0];
end
else if (opcode
ldm)
begin
wa<=instruction[10:8];
port_addr<=instruction[7:0];
end
else if (opcode==ret)
begin
rd_en<=1;
end
end
ldi:
begin
selk<=1;
KTE<=instruction[7:0];
we<=1;
wa<=instruction[10:8];
end
ldm:
begin
wa<=instruction[10:8];
we<=1;
read_e<=1;
port_addr<=instruction[7:0];
end
stm:
begin
raa<=instruction[10:8];
write_e<=1;
port_addr<=instruction[7:0];
end
cmp:
begin
ldflag<=1;
raa<=instruction[10:8];
rab<=instruction[7:5];
opalu<=6;
end
add:
begin
raa<=instruction[10:8];
rab<=instruction[7:5];
wa<=instruction[10:8];
insel<=1;
opalu<=5;
we<=1;
end
sub:
begin
raa<=instruction[10:8];
rab<=instruction[7:5];
wa<=instruction[10:8];
insel<=1;
opalu<=6;
we<=1;
end
andi:
begin
raa<=instruction[10:8];
rab<=instruction[7:5];
wa<=instruction[10:8];
insel<=1;
opalu<=1;
we<=1;
end
oor:
begin
raa<=instruction[10:8];
rab<=instruction[7:5];
wa<=instruction[10:8];
insel<=1;
opalu<=3;
we<=1;
end
xori:
begin
raa<=instruction[10:8];
rab<=instruction[7:5];
wa<=instruction[10:8];
insel<=1;
opalu<=2;
we<=1;
end
jmp:
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
end
jpz:
if (z)
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
end
jnz:
if (!z)
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
end
jpc:
if ©
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
end
jnc:
if (!c)
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
end
csr:
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
wr_en<=1;
end
ret:
begin
naddress<=stack_addr;
selpc<=1;
ldpc<=1;
end
adi:
begin
raa<=instruction[10:8];
wa<=instruction[10:8];
imm<=instruction[7:0];
selimm<=1;
insel<=1;
opalu<=5;
we<=1;
end
csz:
if (z)
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
wr_en<=1;
end
cnz:
if (!z)
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
wr_en<=1;
end
csc:
if ©
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
wr_en<=1;
end
cnc:
if (!c)
begin
naddress<=instruction[10:0];
selpc<=1;
ldpc<=1;
wr_en<=1;
end
sl0:
begin
raa<=instruction[10:8];
wa<=instruction[10:8];
insel<=1;
sh<=0;
we<=1;
end
sl1:
begin
raa<=instruction[10:8];
wa<=instruction[10:8];
insel<=1;
sh<=5;
we<=1;
end
sr0:
begin
raa<=instruction[10:8];
wa<=instruction[10:8];
insel<=1;
sh<=2;
we<=1;
end
sr1:
begin
raa<=instruction[10:8];
wa<=instruction[10:8];
insel<=1;
sh<=6;
we<=1;
end
rrl:
begin
raa<=instruction[10:8];
wa<=instruction[10:8];
insel<=1;
sh<=1;
we<=1;
end
rrr:
begin
raa<=instruction[10:8];
wa<=instruction[10:8];
insel<=1;
sh<=3;
we<=1;
end
noti:
begin
raa<=instruction[10:8];
wa<=instruction[10:8];
insel<=1;
opalu<=0;
we<=1;
end
nop: opalu<=4;
endcase
end

(3)保存模型(存储文件夹路径不能有空格和中文),运行并检查有无错误输出。
8. Natalius processor模型设计
(1)新建一个模型命名为processor,类型为module,同时具备3输入4输出,每个引脚的属性和名称参照下图6-2-19进行对应的修改。
实例十三 Natalius 8位RISC处理器_第20张图片
实例十三 Natalius 8位RISC处理器_第21张图片
(2)添加代码。点击模型下方的 Code添加代码。
该模块和data_path模块类似,仅仅是把control unit, data path和instruction memory三个模块整合连接起来,所以并无代码。
(3)保存模型(存储文件夹路径不能有空格和中文),运行并检查有无错误输出。
9. processor_test 测试文件的设计
(1)由于Robei软件目前尚不支持$readmemh()函数,故本设计的仿真测试在Modelsim中进行。
(2)将之前设计好的所有模块的源代码全部复制到一个文件夹中,并新建一个Modelsim project。
(3)新建一个文件命名为“test_processor.v”,并将以下测试代码复制到文件中。
`timescale 1ns / 1ps
module testbench_processor();
reg clk_tb;
reg rst_tb;
reg [7:0] data_in_tb;
wire [7:0] port_addr_tb;
wire read_e_tb;
wire write_e_tb;
wire [7:0] data_out_tb;

processor processor_i(
.clk(clk_tb),
.rst(rst_tb),
.port_addr(port_addr_tb),
.read_e(read_e_tb),
.write_e(write_e_tb),
.data_in(data_in_tb),
.data_out(data_out_tb)
);
initial begin
clk_tb = 0;
rst_tb = 1;
data_in_tb = 0;
#5 rst_tb = 0;
#2000 $finish;
end
always #2 clk_tb = ~clk_tb;
endmodule

(4)一定要将“2.4汇编器脚本使用”中生成好的“instructions.mem”文件放置在该project所在的同一个目录中,然后才可以进行仿真。
(5)进行仿真并查看波形。根据之前设计的代码内容,查看分析仿真结果。
本次仿真使用的“test.asm”如下
ldi r1, 22
ldi r2, 80
ldi r3, 36
ldi r4, 45
add r1, r2
stm r1, 11
add r3, r4
stm r3, 25
add r1, r3
stm r1, 32
使用Python转化之后的结果如下图6-2-21所示
实例十三 Natalius 8位RISC处理器_第22张图片
转换成“instructions.mem”之后,并进行仿真得到的波形如下图所示:
实例十三 Natalius 8位RISC处理器_第23张图片
经验证,仿真结果正确无误。

6.2.3. 问题与挑战

可以尝试使用Natalius指令集中更多类型的指令,实现更加复杂的功能。

你可能感兴趣的:(FPGA,Robei,教育,高校,Robei案例)