碎碎念
这个的设计我是依照在看的一本课本写的,课本里的编程习惯挺奇怪的,状态机编写思维和我以前的写法不太一样,但是我还是暂且学习他的,然后整体综合后应该有测试和汇编的,我整的比较慢,可能要等一会,在学习一下,而且要快期末了,没这么有空
CPU概述
CPU 即中央处理单元的英文缩写,它是计算机的核心部件。计算机进行信息处理可分为两个步骤:
(1)将数据和程序(即指令序列)输入到计算机的存储器中。
(2)从第一条指令的地址起开始执行该程序,得到所需结果,结束运行。CPU 的作用是协调并控制计算机的各个部件并执行程序的指令序列,使其有条不紊地进行。
因此它必须具有以下基本功能:
①取指令——当程序已在存储器中时,首先根据程序入口地址取出一条程序,为此要发出指令地址及控制信号。
②分析指令——即指令译码,这是对当前取得的指令进行分析,指出它要求什么操作,并产生相应的操作控制命令。
③执行指令——根据分析指令时产生的“操作命令”形成相应的操作控制信号序列,通过运算器、存储器及输入/输出设备的执行,实现每条指令的功能,其中包括对运算结果的处理以及下条指令地址的形成。
将CPU的功能进一步细化,可概括如下:
(1)能对指令进行译码并执行规定的动作;
(2)可以进行算术和逻辑运算;
(3)能与存储器和外设交换数据;
(4)提供整个系统所需要的控制。 尽管各种CPU的性能指标和结构细节各不相同,但它们所能完成的基本功能相同。
由功能分析,可知任何一种CPU 内部结构至少应包含下面这些部件:
(1) 算术逻辑运算部件(ALU);
(2)累加器;
(3)程序计数器;
(4)指令寄存器和译码器;
(5)时序和控制部件。
RISC 即精简指令集计算机(Reduced Instruction Set Computer)的缩写。它是一种 20世纪80年代才出现的CPU,与一般的CPU 相比不仅只是简化了指令系统,而且还通过简化指令系统使计算机的结构更加简单合理,从而提高了运算速度。从实现的途径看,RISC_CPU与一般的CPU 的不同之处在于:它的时序控制信号形成部件是用硬布线逻辑实现的而不是采用微程序控制的方式。所谓硬布线逻辑也就是用触发器和逻辑门直接连线所构成的状态机和组合逻辑,故产生控制序列的速度比用微程序控制方式快得多,因为这样做省去了读取微指令的时间。RISC_CPU也包括上述这些部件。
内部模块
时钟发生器
生成fetch,控制取指令,alu_en控制ALU
module clkgen(
input clk,
input rstn,
output fetch, //fetch驱动CPU控制器执行下一个指令
output alu_en); //使能ALU,实现CPU对ALU的稳定控制
parameter S1 = 8'b0000_0001,
S2 = 8'b0000_0001,
S3 = 8'b0000_0001,
S4 = 8'b0000_0001,
S5 = 8'b0000_0001,
S6 = 8'b0000_0001,
S7 = 8'b0000_0001,
S8 = 8'b0000_0001,
IDLE = 8'b0000_0000;
reg [7:0] state;
reg fetch_r;
reg alu_en_r;
assign fetch = fetch_r;
assign alu_en = alu_en_r;
always @(posedge clk or posedge rstn) begin
if(rstn) begin
fetch_r <= 1'b0;
alu_en_r <= 1'b0;
end else begin
case(state)
S1 : begin
alu_en_r <= 1'b1;
state <= S2;
end
S2 : begin
alu_en_r <= 1'b0;
state <= S3;
end
S3 : begin
fetch_r <= 1'b1;
state <= S4;
end
S4 : state <= S5;
S5 : state <= S6;
S6 : state <= S7;
S7 : begin
fetch_r <= 1'b0;
state <= S8;
end
S8 : state <= S1;
IDLE : state <= S1;
endcase
end
end
endmodule
指令寄存器
在每个时钟上升沿触发,使能时将数据依次放入高/低位
module ir_register(
input clk,
input rstn,
input en, //data是否能被接受的使能,保证指令寄存器接受到正确的指令码
input [7:0] data,
output [15:0] opc_addr); //将高八位和低八位数据拼接输出,高3位是操作码,低13位是地址
reg [15:0] opc_addr_r;
assign opc_addr = opc_addr_r;
reg state;
always @(posedge clk or posedge rstn) begin
if(rstn) begin
state <= 1'b0;
opc_addr_r <= 16'h0000;
end else begin
if(en) begin
case(state)
1'b0 : begin
opc_addr_r [15:8] <= data;
state <= 1'b1;
end
1'b1 : begin
opc_addr_r [7:0] <= data;
state <= 1'b0;
end
default : begin
opc_addr_r <= 16'hxxxx;
state <= 1'bx;
end
endcase
end else state <= 1'b0;
end
end
endmodule
累加器
某些时候暂存某个数据由于运算操作等
module accum(
input clk,
input rstn,
input en,
input [7:0] data,
output [7:0] accum);
reg [7:0] accum_r;
always @(posedge clk or posedge rstn) begin
if(rstn) begin
accum_r <= 8'h00;
end else if(en) begin
accum_r <= data;
end
end
endmodule
算术逻辑运算
ALU,计算机的核心,实现加减算数运算,与异或的逻辑运算和跳转等指令
module alu(
input clk,
input en,
input [2:0] opcode,
input [7:0] data,accum,
output alu_out,
output zero);
reg [7:0] alu_out_r;
reg zero_r;
parameter HLT = 3'b000, //停机指令(Halt),用于停止CPU的执行,暂停所有操作
SKZ = 3'b001, //跳过零指令(Skip if Zero),如果ALU的结果为零,则跳转到指定地址执行下一条指令
ADD = 3'b010, //加法指令(Add),执行加法运算
ANDD = 3'b011, //与操作指令(AND),执行逻辑与运算
XORR = 3'b100, //异或操作指令(XOR),执行逻辑异或运算
LDA = 3'b101, //装入累加器指令(Load Accumulator),将指定的数据加载到累加器中
STO = 3'b110, //存储累加器指令(Store Accumulator),将累加器中的数据存储到指定的位置
JMP = 3'b111; //无条件跳转指令(Jump),无条件跳转到指定地址执行下一条指令
assign zero = !accum;
always @(posedge clk) begin
if(en) begin
casex(opcode)
HLT : alu_out_r <= accum;
SKZ : alu_out_r <= accum;
ADD : alu_out_r <= data + accum;
ANDD : alu_out_r <= data & accum;
XORR : alu_out_r <= data ^ accum;
LDA : alu_out_r <= data;
STO : alu_out_r <= accum;
JMP : alu_out_r <= accum;
default : alu_out_r <= 8'hxx;
endcase
end
end
endmodule
数据控制器
数据控制器,用于控制累加器和ALU的数据输出,保证不输出时不干扰CPU数据总线
module datactl(
input [7:0] in,
input datactl_en,
output [7:0] data);
assign data = (datactl_en) ? in : 8'hzz;
endmodule
地址多路器
选择输出是PC(程序计数器)地址或数据/端口地址,由fetch控制,使前四个时钟读取PC,后四个时钟读取IR_ADDR
module addr(
input [12:0] pc_addr,ir_addr,
input fetch,
output [12:0] addr);
assign addr = (fetch) ? ir_addr : pc_addr;
endmodule
程序计数器
用于提供指令地址,以便读取指令,指令按地址顺序存放在存储器中
有两种途径可形成指令地址:
其一是顺序执行的情况,pc_addr自增
其二是遇到要改变顺序执行程序的情况,例如执行JMP指令后,需要形成新的指令地址,载入ir_addr
module counter(
input clk, //连接inc_pc
input rstn,
input [12:0] ir_addr,
input load,
output [12:0] pc_addr);
reg [12:0] pc_addr_r;
always @(posedge clk or posedge rstn) begin
if(rstn) begin
pc_addr_r <= 13'h00000;
end else begin
if(load) begin
pc_addr_r <= ir_addr;
end else pc_addr_r <= pc_addr_r + 1;
end
end
endmodule
状态控制器
连接逻辑
控制器
状态控制器,控制状态机的使能
module machinectl(
input clk,rstn,fetch,
output en);
reg en_r;
reg state;
always @(posedge clk or posedge rstn) begin
if(rstn) begin
en_r <= 'b0;
end else begin
if(fetch) en_r <= 1'b1;
end
end
endmodule
状态机
状态机,CPU的重要控制核心,产生控制信号,在8个时钟周期执行1个指令周期
第0个时钟,CPU状态控制器的输出rd和load _ ir 为高电平,其余均为低电平。指令寄存器寄存由 ROM送来的高8位指令代码
第1个时钟,与上一时钟相比只是inc _ pc从0变为1,故PC增1,ROM送来低8位指令代码,指令寄存器寄存该8位代码
第2个时钟,空操作
第3个时钟,PC增1,指向下一条指令。若操作符为HLT,则输出信号 HLT为高。如果操作符不为HLT,除了PC增一外(指向下一条指令),其他各控制线输出为零
第4个时钟,若操作符为AND,ADD,XOR 或LDA,读相应地址的数据;若为JMP,将目的地址送给程序计数器;若为 STO,输出累加器数据
第5个时钟,若操作符为ANDD,ADD或XORR,算术运算器就进行相应的运算;
若为LDA,就把数据通过算术运算器送给累加器;若为SKZ,先判断累加器的值是否为0,如果为0,PC就增1,否则保持原值;若为JMP,锁存目的地址;若为STO,将数据写入地址处
第6个时钟,空操作
第7个时钟,若操作符为SKZ 且累加器值为0,则PC值再增1,跳过一条指令,否则PC 无变化
module machine(
input clk,en,zero,
input [2:0] opcode,
output inc_pc, //程序计数器的时钟
output load_acc, //将数据加载到累加器Accum的使能信号
output load_pc, //将一个某个地址加载到程序计数器的使能信号
output rd, //从存储器读取数据
output wr, //向存储器写入数据
output load_ir, //将数据加载到指令寄存器ir_register的使能信号
output datactl, //控制数据路径,可能用于选择数据源或目标,总线控制,使能数据控制器datactl
output halt); //停止CPU的执行,用于响应HLT
reg inc_pc_r;
reg load_acc_r;
reg load_pc_r;
reg rd_r;
reg wr_r;
reg load_ir_r;
reg datactl_r;
reg halt_r;
reg [2:0] state;
parameter HLT = 3'b000,
SKZ = 3'b001,
ADD = 3'b010,
ANDD = 3'b011,
XORR = 3'b100,
LDA = 3'b101,
STO = 3'b110,
JMP = 3'b111;
always @(negedge clk) begin
if(!en) begin
state <= 3'b000;
inc_pc_r <= 'b0;
load_acc_r <= 'b0;
load_pc_r <= 'b0;
rd_r <= 'b0;
wr_r <= 'b0;
load_ir_r <= 'b0;
datactl_r <= 'b0;
halt_r <= 'b0;
end else ctl_cycle;
end
task ctl_cycle; //状态机任务
begin
case(state)
3'b000 : begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b0001;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0100;
state <= 3'b001;
end
3'b001 : begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b1001;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0100;
state <= 3'b010;
end
3'b010 : begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b0000;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0000;
state <= 3'b011;
end
3'b011 : begin
if(opcode == HLT) begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b1000;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0001;
end else begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b1000;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0000;
end
state <= 3'b100;
end
3'b100 : begin
if(opcode == JMP) begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b0010;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0000;
end else if(opcode == ADD||opcode == ANDD||opcode == XORR||opcode == LDA)begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b0001;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0000;
end else if(STO) begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b0000;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0010;
end else begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b0000;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0000;
end
state <= 3'b101;
end
3'b101 : begin
if(opcode == ADD||opcode == ANDD||opcode == XORR||opcode == LDA) begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b0101;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0000;
end else if(opcode == JMP)begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b0001;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0000;
end else if(opcode == SKZ&&zero == 1) begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b1000;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0000;
end else if(opcode == STO) begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b0000;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b1010;
end else begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b0000;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0000;
end
state <= 3'b110;
end
3'b110 : begin
if(opcode == STO) begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b0000;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0010;
end else if(opcode == ADD||opcode == ANDD||opcode == XORR||opcode == LDA)begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b0001;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0000;
end else begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b0000;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0000;
end
state <= 3'b111;
end
3'b111 : begin
if(opcode == SKZ&&zero == 1) begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b1000;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0000;
end else begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b0000;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0000;
end
state <= 3'b000;
end
default : begin
{inc_pc_r,load_acc_r,load_pc_r,rd_r} <= 4'b0000;
{wr_r,load_ir_r,datactl_r,halt_r} <= 4'b0000;
state <= 3'b000;
end
endcase
end
endtask
endmodule
外部模块
地址译码器
地址译码器,选择到RAM或ROM的使能
module addr_decode(
output rom_sel,ram_sel,
input [12:0] addr);
reg rom_sel_r,ram_sel_r;
always @(addr) begin
casex(addr)
13'b1_1xxx_xxxx_xxxx : {rom_sel_r,ram_sel_r} <= 2'b01;
13'b0_xxxx_xxxx_xxxx : {rom_sel_r,ram_sel_r} <= 2'b10;
13'b1_0xxx_xxxx_xxxx : {rom_sel_r,ram_sel_r} <= 2'b10;
default : {rom_sel_r,ram_sel_r} <= 2'b00;
endcase
end
endmodule
RAM
内存,可读可写,储存数据
module ram(
input [9:0] addr,
inout [7:0] data,
input en,
input read,
input write
);
reg [7:0] ram [10'h3ff:0];
// 当使能且读使能为高时,将ram[addr]的数据赋给data;否则data为高阻态
assign data = (en && read) ? ram[addr] : 8'bz;
// 写操作
always @(posedge write) begin
if (en && write) begin
ram[addr] <= data;
end
end
endmodule
ROM
用于装载测试程序,只读不写
module rom(
input en,
input read,
input [9:0] addr,
output [7:0] data
);
reg [7:0] rom [10'h3ff:0];
// 当使能且读使能为高时,将rom[addr]的数据赋给data;否则data为高阻态
assign data = (en && read) ? rom[addr] : 8'bz;
endmodule
CPU模块的综合
module risc_cpu(
input clk,
input rstn, //高复位
output [12:0] addr,
output rd,wr,halt,
inout [7:0] data,
//下面是调试用
output [2:0] opcode_o,
output [12:0] ir_addr_o,pc_addr_o,
output fetch_o);
wire [12:0] ir_addr,pc_addr;
wire [2:0] opcode;
wire alu_en;
wire inc_pc;
wire [7:0] alu_out,accum;
wire fetch;
wire load_ir,load_pc,load_acc;
wire zero;
wire datactl_en,control_en;
assign opcode_o = opcode;
assign ir_addr_o = ir_addr;
assign pc_addr_o = pc_addr;
assign fetch_o = fetch;
clkgen m_clkgen(
.clk(clk),
.rstn(rstn),
.fetch(fetch),
.alu_en(alu_en));
ir_register m_ir_register(
.clk(clk),
.rstn(rstn),
.en(load_ir),
.opc_addr({opcode,ir_addr}),
.data(data));
accum m_accum(
.clk(clk),
.rstn(rstn),
.en(load_acc),
.data(alu_out),
.accum(accum));
alu m_alu(
.clk(clk),
.en(alu_en),
.opcode(opcode),
.data(data),
.accum(accum),
.alu_out(alu_out),
.zero(zero)
);
datactl m_datactl(
.in(alu_out),
.datactl_en(datactl_en),
.data(data));
addr m_addr(
.pc_addr(pc_addr),
.ir_addr(ir_addr),
.fetch(fetch),
.addr(addr));
counter m_counter(
.clk(inc_pc),
.rstn(rstn),
.ir_addr(ir_addr),
.pc_addr(pc_addr),
.load(load_pc));
machinectl m_machinectl(
.clk(clk),
.rstn(rstn),
.fetch(fetch),
.en(control_en));
machine m_machine(
.clk(clk),
.en(control_en),
.zero(zero),
.opcode(opcode),
.inc_pc(inc_pc),
.load_acc(load_acc),
.load_pc(load_pc),
.rd(rd),
.wr(wr),
.load_ir(load_ir),
.datactl(datactl_en),
.halt(halt));
endmodule
与外部模块的整体综合
module cputop;
reg rstn,clk;
integer test;
reg [(3*8):0] mnemonic;
reg [12:0] PC_addr,IR_addr;
wire [12:0] pc_addr,ir_addr;
wire [7:0] data;
wire [12:0] addr;
wire rd,wr,halt,ram_sel,rom_sel;
wire [2:0] opcode;
wire fetch;
cpu m_cpu(
.clk(clk),
.rstm(rstn),
.halt(halt),
.rd(rd),
.wr(wr),
.addr(addr),
.data(data),
.opcode_o(opcode),
.ir_addr_o(ir_addr),
.pc_addr_o(pc_addr));
ram m_ram(
.addr(addr),
.en(ram_sel),
.read(rd),
.write(wr),
.data(data));
rom m_rom(
.en(rom_sel),
.read(rd),
.addr(addr),
.data(data));
addr_decode m_addr_decode(
.addr(addr),
.ram_sel(ram_sel),
.rom_sel(rom_sel));
endmodule