我们上一篇文章讲述了RISC_CPU的结构,但是要验证RISC_CPU能否正确工作,还需要一些外围电路来提供ROM(测试程序),RAM(装载数据)以及地址译码器。下面我们将一一做介绍。
1、RISC_CPU寻址方式和指令系统
RISC_CPU的一条指令有16位,其中前3位是指令opcode,后13位是地址ir_addr。
操作码opcode有8种操作,由着8中操作可以实现任意的运算。
- HLT,停机操作。
- SKZ,如果累加器的输出accum为0,则跳过下一条语句,否则继续执行。
- ADD,将累加器中的值与地址所指的ram中的数据相加,结果仍然送回累加器。
- AND,将累加器中的值与地址所指的ram中的数据相与,结果仍然送回累加器。
- XOR,将累加器中的值与地址所指的ram中的数据相异或,结果仍然送回累加器。
- LDA,将指令中给出的地址的数据放入累加器。
- STO,将累加器上的数据放入指令中给出的地址。
- JMP,无条件跳转语句,跳转指令给出的目的地址,继续执行。
RISC_CPU,是8位微处理器,一律采用直接寻址的方式,即数据总放在存储器中,寻址单元由指令直接给出。这是最简单的寻址方式。
2、外围电路
对RISC_CPU进行调试的话,需要外围电路提供ROM(测试程序),RAM(装载数据)以及地址译码器。
地址译码器用于产生选通信号,选通ROM或RAM。
module addr_decode (
addr,
ram_sel,
rom_sel
);
input[12:0] addr;
output ram_sel,rom_sel;
reg rom_sel,ram_sel;
always@(addr)
begin
casex(addr)
13'b1_1xxx_xxxx_xxxx:{rom_sel,ram_sel}=2'b01;
13'b0_xxxx_xxxx_xxxx:{rom_sel,ram_sel}=2'b10;
13'b1_0xxx_xxxx_xxxx:{rom_sel,ram_sel}=2'b10;
default:{rom_sel,ram_sel}=2'b00;
endcase
end
endmodule
13'h1FFF-----13'h1800 RAM
13'h17FF-----13'h0000 ROM
module ram_cpu (
addr,
rd,
wr,
ena,
data
);
input[12:0] addr;
input rd,wr,ena;
inout[7:0] data;
reg[7:0] memory_ram [13'h1fff:13'h1800];
assign data=(rd&&ena)?memory_ram[addr]:8'bzzzzzzzz;
always@(posedge wr)
begin
memory_ram[addr]<=data;
end
endmodule
RAM用于存放的数据,可读可写。
module rom_cpu (
addr,
rd,
ena,
data
);
input[12:0] addr;
input rd,ena;
output[7:0] data;
wire[7:0] data;
reg[7:0] memory_rom [13'h17ff:13'h0000];
assign data=(ena&&rd)?memory_rom[addr]:8'bzzzzzzzz;
endmodule
ROM用于装载测试程序。
3、连接外围电路测试RSIC_CPU
/*`include "cpu.v"
`include "ram_cpu"
`include "rom_cpu"
`include "addr_decode"*/
`timescale 1ns/1ns
//`define PERIOD 100
module cpu_top;
reg rst,clk;
reg[(3*8):0] mnemonic;
reg[12:0] PC_addr,IR_addr;
wire[7:0] data;
wire HALT,rd,wr,ram_sel,rom_sel;
wire fetch;
wire[12:0] addr;
integer test;
// wire[2:0] opcode;
// wire[12:0]ir_addr,pc_addr;
cpu cpu_t( .clk(clk),
.rst(rst),
.data(data),
.rd(rd),
.wr(wr),
.addr(addr),
.HALT(HALT),
.fetch(fetch));
ram_cpu ram_cpu_t(.addr(addr),
.rd(rd),
.wr(wr),
.ena(ram_sel),
.data(data));
rom_cpu rom_cpu_t(.addr(addr),
.rd(rd),
.ena(rom_sel),
.data(data));
addr_decode addr_decode_t(.addr(addr),
.ram_sel(ram_sel),
.rom_sel(rom_sel));
initial
begin
clk=0;
$timeformat (-9,1,"ns",12);
display_debug_message;
asyn_rst;
//test1;
//test2;
//test3;
test4;
$finish;
end
task display_debug_message;
begin
$display("\n*********************");
$display("load test");
$display("***********************\n");
end
endtask
task asyn_rst;
begin
rst=1;
#10
rst=0;
#40
rst=1;
end
endtask
/*task test1;
begin
test=0;
disable MONITOR;
$readmemb("C:/Users/XQ/Desktop/test_pro.txt",rom_cpu_t.memory_rom);
$display("rom loaded successfully!");
$readmemb("C:/Users/XQ/Desktop/test_dat.txt",ram_cpu_t.memory_ram);
$display("ram loaded successfully!");
#1 test=1;
#10000;
asyn_rst;
end
endtask
always@(test)
begin:MONITOR
$display("\n***running test1***");
$display("\n TIME PC INSTRUCTION ADDR DATA");
$display(" ------- ------ ----------- ------ ------");
while (test==1)
@(cpu_t.pc_addr)
if((cpu_t.pc_addr%2==1)&&(cpu_t.fetch==1))
begin
#30 PC_addr<=cpu_t.pc_addr-1;
IR_addr<=cpu_t.ir_addr;
#130 $strobe("%t %h %s %h %h",$time,PC_addr,mnemonic,IR_addr,data);
end
end*/
/*task test2;
begin
test=0;
disable MONITOR;
$readmemb("C:/Users/XQ/Desktop/test_pro.txt",rom_cpu_t.memory_rom);
$display("rom loaded successfully!");
$readmemb("C:/Users/XQ/Desktop/test_dat.txt",ram_cpu_t.memory_ram);
$display("ram loaded successfully!");
#1 test=1;
#10000;
asyn_rst;
end
endtask
always@(test)
begin:MONITOR
$display("\n***running test2***");
$display("\n TIME PC INSTRUCTION ADDR DATA");
$display(" ------- ------ ----------- ------ ------");
while (test==1)
@(cpu_t.pc_addr)
if((cpu_t.pc_addr%2==1)&&(cpu_t.fetch==1))
begin
#30 PC_addr<=cpu_t.pc_addr-1;
IR_addr<=cpu_t.ir_addr;
#130 $strobe("%t %h %s %h %h",$time,PC_addr,mnemonic,IR_addr,data);
end
end*/
/*task test3;
begin
test=0;
disable MONITOR;
$readmemb("C:/Users/XQ/Desktop/test_pro.txt",rom_cpu_t.memory_rom);
$display("rom loaded successfully!");
$readmemb("C:/Users/XQ/Desktop/test_dat.txt",ram_cpu_t.memory_ram);
$display("ram loaded successfully!");
#1 test=1;
#1000000;
asyn_rst;
end
endtask
always@(test)
begin:MONITOR
$display("\n***running test3***");
$display("\n TIME FN1 FN2 TEMP");
$display(" ------- ------ ------ ----- ");
while (test==1)
begin
wait (cpu_t.opcode==3'h1)
$strobe("%t %d %d %d",$time,ram_cpu_t.memory_ram[13'h1800],ram_cpu_t.memory_ram[13'h1801],ram_cpu_t.memory_ram[13'h1802]);
wait (cpu_t.opcode!=3'h1);
end
end*/
task test4;
begin
test=0;
disable MONITOR;
$readmemb("C:/Users/XQ/Desktop/test_pro.txt",rom_cpu_t.memory_rom);
$display("rom loaded successfully!");
$readmemb("C:/Users/XQ/Desktop/test_dat.txt",ram_cpu_t.memory_ram);
$display("ram loaded successfully!");
#1 test=1;
#100000;
asyn_rst;
end
endtask
always@(test)
begin:MONITOR
$display("\n***running test4***");
$display("\n TIME FN1 FN2 FN3 TEMP");
$display(" ------- ------- ------- ----- ------");
while (test==1)
begin
wait (cpu_t.opcode==3'h1)
$strobe("%t %d %d %d %d",$time,ram_cpu_t.memory_ram[13'h1800],ram_cpu_t.memory_ram[13'h1801],ram_cpu_t.memory_ram[13'h1802],ram_cpu_t.memory_ram[13'h1804]);
wait (cpu_t.opcode!=3'h1);
end
end
always@(posedge HALT)
begin
#200
$display("\n*********************");
$display("**a HALT instruction was processed**");
$display("*************************\n");
end
always #20 clk=~clk;
always@(cpu_t.opcode)
begin
case(cpu_t.opcode)
3'b000:mnemonic="HLT";
3'b001:mnemonic="SKZ";
3'b010:mnemonic="ADD";
3'b011:mnemonic="AND";
3'b100:mnemonic="XOR";
3'b101:mnemonic="LDA";
3'b110:mnemonic="STO";
3'b111:mnemonic="JMP";
default:mnemonic="???";
endcase
end
endmodule
4、测试程序
在这里我简单列出几个测试程序供大家调试应用。
4.1 验证CPU逻辑功能的机器代码
该测试程序用来验证RISC_CPU的opcode的正确性。
ROM中的程序
@0000
111_00000 //00 begin: JMP TST_JMP
0011_1100
000_00000 //02 HLT //JMP did not work at all
0000_0000
000_00000 //04 HLT //JMP did not load PC,it skipped
0000_0000
101_11000 //06 JMP_OK: LDA DATA_1
0000_0000
001_00000 //08 SKZ
0000_0000
000_00000 //0a HLT //SKZ or LDA did not work
0000_0000
101_11000 //0c LDA DATA_2
0000_0001
001_00000 //0e SKZ
0000_0000
111_00000 //10 JMP SKZ_OK
0001_0100
000_00000 //12 HLT //SKZ or LDA did not work
0000_0000
110_11000 //14 SKZ_OK: STO TEMP //store non-zero vaule in TEMP
0000_0010
101_11000 //16 LDA DATA_1
0000_0000
110_11000 //18 STO TEMP //store zero vaule in TEMP
0000_0010
101_11000 //1a LDA TEMP
0000_0010
001_00000 //1c SKZ //check to see if STO worked
0000_0000
000_00000 //1e HLT //STO did not work
0000_0000
100_11000 //20 XOR DATA_2
0000_0001
001_00000 //22 SKZ //check to see XOR worked
0000_0000
111_00000 //24 JMP XOR_OK
0010_1000
000_00000 //26 HLT //XOR did not work at all
0000_0000
100_11000 //28 XOR_OK: XOR DATA_2
0000_0001
001_00000 //2a SKZ
0000_0000
000_00000 //2c HLT //XOR did not switch all bits
0000_0000
000_00000 //2e END: HLT //CONGRATULATIONS-TEST1 PASSED!
0000_0000
111_00000 //30 JMP BEGIN //run test again
0000_0000
@3c
111_00000 //3c TST_JMP: JMP JMP_OK
0000_0110
000_00000 //3e HLT //JMP is broken
RAM中的数据
@1800
00000000 //1800 DATA_1: //constant 00(hex)
11111111 //1801 DATA_2: //constant FF(hex)
10101010 //1802 TEMP: //variable - starts with AA(hex)
4.2 验证CPU的高级指令集
ROM中存放的程序
@0000
101_11000 //00 begin: LDA DATA_2
0000_0001
011_11000 //02 AND DATA_3
0000_0010
100_11000 //04 XOR DATA_2
0000_0001
001_00000 //06 SKZ
0000_0000
000_00000 //08 HLT //AND doesn't work
0000_0000
010_11000 //0a ADD DATA_1
0000_0000
001_00000 //0c SKZ
0000_0000
111_00000 //0e JMP //ADD_OK
0001_0010
000_00000 //10 HLT //ADD doesn't work
0000_0000
100_11000 //12 ADD_OK:XOR DATA_3
0000_0010
010_11000 //14 ADD DATA_1 //FE plus 1 makes FF
0000_0000
110_11000 //16 STO TEMP
0000_0011
101_11000 //18 LDA DATA_1
0000_0000
010_11000 //1a ADD TEMP //FF plus 1 makes 100(hex)
0000_0011
001_00000 //1c SKZ
0000_0000
000_00000 //1e HLT //ADD doesn't work
0000_0000
000_00000 //20 END: HLT //congratulations test passed
0000_0000
111_00000 //22 JMP begin //run test again
0000_0000
RAM中存放的数据
@1800
00000001 //1800 DATA_1: //constant 01(hex)
10101010 //1801 DATA_2: //constant AA(hex)
11111111 //1802 DATA_3: //constant FF(hex)
00000000 //1803 TEMP: //variable - starts with 00(hex)
4.3 验证CPU的功能-Fibonacci序列
所谓Fibonacc序列,就是后一个数是前两个数相加的和,0、1、1、2、3、5、8、13、21、34......
(1)将FN2放入TEMP
(2)将FN1+FN2的结果放入FN2
(3)将TEMP放入FN1
(4)将FN1与LIMIT做异或操作,决定循环是否继续进行
ROM中的指令
@0000
101_11000 //00 LOOP: LDA FN2 //load value in FN2 into accum
0000_0001
110_11000 //02 STO TEMP //store accum in TEMP
0000_0010
010_11000 //04 ADD FN1
0000_0000
110_11000 //06 STO FN2
0000_0001
101_11000 //08 LDA TEMP
0000_0010
110_11000 //0a STO FN1
0000_0000
100_11000 //0c XOR LIMIT
0000_0011
001_00000 //0e SKZ
0000_0000
111_00000 //10 JMP LOOP
0000_0000
000_00000 //12 DONE: HLT
0000_0000
RAM中的数据
@1800
00000001 //1800 FN1
00000000 //1801 FN2
00000000 //1802 TEMP
10010000 //1803 LIMIT
4.4 验证CPU的功能-阶乘
此程序的功能就是计算5!
其中:
- FN2用于计算累加的次数
- FN3用于计算是否达到LIMIT
- TEMP用于存放n!的结果
ROM中的程序
@00
101_11000 //00 LOOP1: LDA TEMP
0000_0100
010_11000 //02 ADD FN1
0000_0000
110_11000 //04 STO TEMP
0000_0100
101_11000 //06 LDA FN2
0000_0001
010_11000 //08 ADD DATA
0000_0101
110_11000 //0a STO FN2
0000_0001
100_11000 //0c XOR FN3
0000_0010
001_00000 //0e SKZ
0000_0000
111_00000 //10 JMP LOOP1
0000_0000
101_11000 //12 LDA FN3
0000_0010
010_11000 //14 ADD DATA
0000_0101
110_11000 //16 STO FN3
0000_0010
101_11000 //18 LDA DATA
0000_0101
110_11000 //1a STO FN2
0000_0001
101_11000 //1c LDA TEMP
0000_0100
110_11000 //1e STO FN1
0000_0000
101_11000 //20 LDA FN3
0000_0010
100_11000 //22 XOR LIMIT
0000_0011
001_00000 //24 SKZ
0000_0000
111_00000 //26 JMP LOOP1
0000_0000
000_00000 //28 DONE: HLT
0000_0000
RAM中的数据
@00
0000_0001 //1800 FN1
0000_0001 //1801 FN2
0000_0010 //1802 FN3
0000_0110 //1803 LIMIT
0000_0001 //1804 TEMP
0000_0001 //1805 DATA