数字IC实践项目(1)——简化的RISC_CPU设计
这个实践项目来源于夏宇闻老师的经典教材——《Verilog 数字系统设计教程》,也是我本科期间的专业教材之一,每次看到这个蓝色的封面都感到很亲切。而对于书中提及到的简化CPU,也是从大学开始就非常感兴趣的一个章节,虽然本科老师只是简单的带过,但是一直对书里提到的CPU结构以及最后使用CPU完成斐波那契数列计算的整个流程充满了兴趣。
这里也是怀揣着敬佩之心,对这个简化的RISC_CPU完成复刻,虽然整个项目是偏向教学目的,结构和功能也是非常简单,甚至在今天这种就业环境下没法写入到项目经历中去,但是在整个项目过程中能锻炼自己的Coding Style和设计技能。
项目难度:⭐⭐
项目推荐度:⭐⭐⭐
项目推荐天数:0.5~1天
夏宇闻老师的经典教材——《Verilog 数字系统设计教程》:

这个项目主要针对教学,是作为学习Verilog语法后的一个练手项目,更多的是了解CPU内部构成,练习Verilog语法和Coding Style,熟悉设计工具以及锻炼Debug能力。
项目实践环境:
前仿: Modelsim SE-64 2019.2
综合: Quartus (Quartus Prime 17.1) Standard Edition
项目学习目的:
(1)学习实践项目工程管理;
(2)熟悉Verilog HDL仿真和FPGA综合工具;
(3)学习RISC_CPU基本结构和基础原理;
(4)练习Verilog语法和验证方法;
(5)熟练掌握Modelsim。
CPU(Central Processing Unit),中文全称中央处理器,作为四大U之首(CPU/GPU/TPU/NPU),是计算机系统的运算和控制核心,也是当今数字系统中不可或缺的组成部分。CPU自诞生到如今发展超过50年,借助冯诺依曼体系,CPU掀起一股又一股的科技浪潮。RISC作为精简了指令集的CPU,除了指令更加简洁,还拥有简单合理的内部结构,从而提高了运算速度。
CPU工作的5个阶段:
(1)取指(IF,Instruction Fetch),将指令从存储器取出到指令寄存器。每取一条指令,程序计数器自加一。
(2)译指(ID,Instruction Decode),对取出的指令按照规定格式进行拆分和译码。
(3)执行(EX,Execute),执行具体指令操作。
(4)访问存储(MEM,Memory),根据指令访问存储、完成存储和读取。
(5)写回(WB,Write Back),将计算结果写回到存储器。
CPU内部关键结构:
(1)算术逻辑运算器(ALU);
(2)累加器;
(3)程序计数器;
(4)指令寄存器和译码器;
(5)时序和控制部件。
本项目中的RISC_CPU一共有9个模块组成,具体如下:
(1)时钟发生器;
(2)指令寄存器;
(3)累加器;
(4)算术逻辑运算单元;
(5)数据控制器;
(6)状态控制器;
(7)主状态机;
(8)程序计数器;
(9)地址多路器。
在Modelsim中的电路图如下:

模块图:

端口描述:
reset是高电平复位信号;
clk是外部时钟信号;
fetch是控制信号,是clk的八分频信号;fetch为高电平时,触发执行指令以及地址多路器输出指令地址和数据地址。
alu_ena是算术逻辑运算单元的使能信号。
仿真波形:

可以看到alu_ena提前fetch高电平一个clk周期,fetch是clk的8分频信号。
Verilog代码:
这里按照原文思路来复现。
// Description: RISC——CPU 时钟发生器
// -----------------------------------------------------------------------------
module clk_gen (
input clk , // Clock
input reset , // High level reset
output reg fetch , // 8 frequency division
output reg alu_ena // Arithmetic enable
);
reg [7:0] state;
//One-piece state machine
parameter S1 = 8'b0000_0001,
S2 = 8'b0000_0010,
S3 = 8'b0000_0100,
S4 = 8'b0000_1000,
S5 = 8'b0001_0000,
S6 = 8'b0010_0000,
S7 = 8'b0100_0000,
S8 = 8'b1000_0000,
idle = 8'b0000_0000;
always@(posedge clk)begin
if(reset)begin
fetch <= 0;
alu_ena <= 0;
state <= idle;
end
else begin
case(state)
S1:
begin
alu_ena <= 1;
state <= S2;
end
S2:
begin
alu_ena <= 0;
state <= S3;
end
S3:
begin
fetch <= 1;
state <=S4;
end
S4:
begin
state <= S5;
end
S5:
state <= S6;
S6:
state <= S7;
S7:
begin
fetch <= 0;
state <= S8;
end
S8:
begin
state <= S1;
end
idle: state <= S1;
default: state <=idle;
endcase
end
end
endmodule
模块图:

端口描述:
寄存器是将数据总线送来的指令存入高8位或低8位寄存器中。
ena信号用来控制是否寄存。
每条指令为两个字节,16位,高3位是操作码,低13位是地址(CPU地址总线为13位,寻址空间为8K字节)。
本设计的数据总线为8位,每条指令需要取两次,先取高8位,再取低8位。
Verilog代码:
// Description: RISC—CPU 指令寄存器
// -----------------------------------------------------------------------------
module register (
input [7:0] data ,
input clk ,
input rst ,
input ena ,
output reg [15:0] opc_iraddr
);
reg state ;
//
always@( posedge clk ) begin
if( rst ) begin
opc_iraddr <= 16'b 0000_0000_0000_0000;
state <= 1'b 0;
end // if rst
// If load_ir from machine actived, load instruction data from rom in 2 clock periods.
// Load high 8 bits first, and then low 8 bits.
else if( ena ) begin
case( state )
1'b0 : begin opc_iraddr [ 15 : 8 ] <= data;
state <= 1;
end
1'b1 : begin opc_iraddr [ 7 : 0 ] <= data;
state <= 0;
end
default : begin opc_iraddr [ 15 : 0 ] <= 16'bxxxx_xxxx_xxxx_xxxx;
state <= 1'bx;
end
endcase // state
end // else if ena
else state <= 1'b0;
end
endmodule
模块图:

端口描述:
累加器用于存放当前结果,ena信号有效时,在clk上升沿输出数据总线的数据。
Verilog代码:
// Description: RISC-CPU 累加器模块
// -----------------------------------------------------------------------------
module accum (
input clk , // Clock
input ena , // Enable
input rst , // Asynchronous reset active high
input [7:0] data , // Data bus
output reg [7:0] accum
);
always@(posedge clk)begin
if(rst)
accum <= 8'b0000_0000;//Reset
else if(ena)
accum <= data;
end
endmodule
模块图:

端口描述:
算术逻辑运算单元可以根据输入的操作码分别实现相应的加、与、异或、跳转等基本操作运算。
本单元支持8种操作运算。
opcode用来选择计算模式
data是数据输入
accum是累加器输出
alu_ena是模块使能信号
clk是系统时钟
⭐这里在做前仿真时遇见一个错误,将代码改动了一下⭐
Verilog代码:
// Description: RISC-CPU 算术运算器
// -----------------------------------------------------------------------------
module alu (
input clk , // Clock
input alu_ena , // Enable
input [2:0] opcode , // High three bits are used as opcodes
input [7:0] data , // data
input [7:0] accum , // accum out
output reg [7:0] alu_out ,
output zero
);
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 @(posedge alu_ena) begin
casex(opcode)
HLT: alu_out <= accum ;
SKZ: alu_out <= accum ;
ADD: alu_out <= data + accum ;
ANDD: alu_out <= data & accum ;
XORR: alu_out <= data ^ accum ;
LDA : alu_out <= data ;
STO : alu_out <= accum ;
JMP : alu_out <= accum ;
default: alu_out <= 8'bxxxx_xxxx ;
endcase
end
assign zero = !accum;
endmodule
模块图:

端口描述:
数据控制器的作用是控制累加器的数据输出,数据总线是分时复用的,会根据当前状态传输指令或者数据。
数据只在往RAM区或者端口写时才允许输出,否则呈现高阻态。
in是8bit数据输入
data_ena是使能信号
data是8bit数据输出
Verilog代码:
// Description: RISC-CPU 数据控制器
// -----------------------------------------------------------------------------
module datactl (
input [7:0] in , // Data input
input data_ena , // Data Enable
output wire [7:0] data // Data output
);
assign data = (data_ena )? in: 8'bzzzz_zzzz ;
endmodule
模块图:

端口描述:
用于选择输出地址是PC(程序计数)地址还是数据/端口地址。每个指令周期的前4个时钟周期用于从ROM种读取指令,输出的是PC地址;后四个时钟周期用于对RAM或端口读写。
地址多路器和数据控制器实现的功能十分相似。
fetch信号用来控制地址输出,高电平输出pc_addr ,低电平输出ir_addr ;
pc_addr 指令地址;
ir_addr ram或端口地址。
Verilog代码:
// Description: RISC-CPU 地址多路器
// -----------------------------------------------------------------------------
module adr (
input fetch , // enable
input [12:0] ir_addr , //
input [12:0] pc_addr , //
output wire [12:0] addr
);
assign addr = fetch? pc_addr :ir_addr ;
endmodule
模块图:

端口描述:
程序计数器用来提供指令地址,指令按照地址顺序存放在存储器中。包含两种生成途径:
(1)顺序执行的情况
(2)需要改变顺序,例如JMP指令
rst复位信号,高电平时地址清零;
clock 时钟信号,系统时钟;
ir_addr目标地址,当加载信号有效时输出此地址;
pc_addr程序计数器地址
load地址装载信号
Verilog代码:
// Description: RISC-CPU 程序计数器
// -----------------------------------------------------------------------------
module counter (
input [12:0] ir_addr , // program address
input load , // Load up signal
input clock , // CLock
input rst , // Reset
output reg [12:0] pc_addr // insert program address
);
always@(posedge clock or posedge rst) begin
if(rst)
pc_addr <= 13'b0_0000_0000_0000;
else if(load)
pc_addr <= ir_addr;
else
pc_addr <= pc_addr + 1;
end
endmodule
模块图:

端口描述:
状态控制器接收复位信号rst,rst有效,控制输出ena为0,fetch有效控制ena为1。
Verilog代码:
// Description: RISC-CPU 状态控制器
// -----------------------------------------------------------------------------
module machinectl (
input clk , // Clock
input rst , // Asynchronous reset
input fetch , // Asynchronous reset active low
output reg ena // Enable
);
always@(posedge clk)begin
if(rst)
ena <= 0;
else if(fetch)
ena <=1;
end
endmodule
模块图:

端口描述:
主状态机是CPU的控制核心,用于产生一系列控制信号。
指令周期由8个时钟周期组成,每个时钟周期都要完成固定的操作。
(1)第0个时钟,CPU状态控制器的输出rd和load_ir 为高电平,其余为低电平。指令寄存器寄存由ROM送来的高8位指令代码。
(2)第1个时钟,与上一个时钟相比只是inc_pc从0变为1,故PC增1,ROM送来低8位指令代码,指令寄存器寄存该8位指令代码。
(3)第2个时钟,空操作。
(4)第3个时钟,PC增1,指向下一条指令。
操作符为HLT,输出信号HLT为高。
操作符不为HLT,除PC增1外,其余控制线输出为0.
(5)第4个时钟,操作。
操作符为AND,ADD,XOR或LDA,读取相应地址的数据;
操作符为JMP,将目的地址送给程序计数器;
操作符为STO,输出累加器数据。
(6)第5个时钟,若操作符为ANDD,ADD或者XORR,算术运算器完成相应的计算;
操作符为LDA,就把数据通过算术运算器送给累加器;
操作符为SKZ,先判断累加器的值是否为0,若为0,PC加1,否则保持原值;
操作符为JMP,锁存目的地址;
操作符为STO,将数据写入地址处。
(7)第6个时钟,空操作。
(8)第7个时钟,若操作符为SKZ且累加器为0,则PC值再加1,跳过一条指令,否则PC无变化。
Verilog代码:
// Description: RISC-CPU 主状态机
// -----------------------------------------------------------------------------
module machine (
input clk , // Clock
input ena , // Clock Enable
input zero , // Asynchronous reset active low
input [2:0] opcode , // OP code
output reg inc_pc , //
output reg load_acc , //
output reg load_pc , //
output reg rd , //
output reg wr , //
output reg load_ir , //
output reg datactl_ena , //
output reg halt
);
reg [2:0] state ;
//parameter
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(!ena) //收到复位信号rst,进行复位操作
begin
state <= 3'b000;
{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
{wr,load_ir,datactl_ena,halt} <= 4'b0000;
end
else
ctl_cycle;
end
//------- task ctl_cycle -------
task ctl_cycle;
begin
casex(state)
3'b000: //load high 8bits in struction
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b0001;
{wr,load_ir,datactl_ena,halt} <= 4'b0100;
state <= 3'b001;
end
3'b001://pc increased by one then load low 8bits instruction
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b1001;
{wr,load_ir,datactl_ena,halt} <= 4'b0100;
state <= 3'b010;
end
3'b010: //idle
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
{wr,load_ir,datactl_ena,halt} <= 4'b0000;
state <= 3'b011;
end
3'b011: //next instruction address setup 分析指令开始点
begin
if(opcode == HLT)//指令为暂停HLT
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b1000;
{wr,load_ir,datactl_ena,halt} <= 4'b0001;
end
else
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b1000;
{wr,load_ir,datactl_ena,halt} <= 4'b0000;
end
state <= 3'b100;
end
3'b100: //fetch oprand
begin
if(opcode == JMP)
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b0010;
{wr,load_ir,datactl_ena,halt} <= 4'b0000;
end
else if(opcode == ADD || opcode == ANDD || opcode == XORR || opcode == LDA)
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b0001;
{wr,load_ir,datactl_ena,halt} <= 4'b0000;
end
else if(opcode == STO)
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
{wr,load_ir,datactl_ena,halt} <= 4'b0010;
end
else
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
{wr,load_ir,datactl_ena,halt} <= 4'b0000;
end
state <= 3'b101;
end
3'b101://operation
begin
if(opcode == ADD || opcode == ANDD ||opcode ==XORR ||opcode == LDA)//过一个时钟后与累加器的内存进行运算
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b0101;
{wr,load_ir,datactl_ena,halt} <= 4'b0000;
end
else if(opcode == SKZ && zero == 1)// & and &&
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b1000;
{wr,load_ir,datactl_ena,halt} <= 4'b0000;
end
else if(opcode == JMP)
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b1010;
{wr,load_ir,datactl_ena,halt} <= 4'b0000;
end
else if(opcode == STO)
begin//过一个时钟后吧wr变为1,写到RAM中
{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
{wr,load_ir,datactl_ena,halt} <= 4'b1010;
end
else
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
{wr,load_ir,datactl_ena,halt} <= 4'b0000;
end
state <= 3'b110;
end
3'b110:
begin
if(opcode == STO)
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
{wr,load_ir,datactl_ena,halt} <= 4'b0010;
end
else if(opcode == ADD || opcode == ANDD || opcode == XORR || opcode == LDA)
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b0001;
{wr,load_ir,datactl_ena,halt} <= 4'b0000;
end
else
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
{wr,load_ir,datactl_ena,halt} <= 4'b0000;
end
state <= 3'b111;
end
3'b111:
begin
if(opcode == SKZ && zero == 1)
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b1000;
{wr,load_ir,datactl_ena,halt} <= 4'b0000;
end
else
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
{wr,load_ir,datactl_ena,halt} <= 4'b0000;
end
state <= 3'b000;
end
default:
begin
{inc_pc,load_acc,load_pc,rd} <= 4'b0000;
{wr,load_ir,datactl_ena,halt} <= 4'b0000;
state <= 3'b000;
end
endcase
end
endtask
endmodule
为了对RISC-CPU进行测试,需要对ROM、RAM和地址译码器进行设计。
模块说明:
地址译码器用于产生选通信号,选通ROM或者RAM
1FFFH —— 1800H RAM
17FFH —— 0000H ROM
Verilog代码:
// Description: RISC-CPU 地址译码器
// -----------------------------------------------------------------------------
module addr_decode (
input [12:0] addr , // Address
output reg ram_sel , // Ram sel
output reg rom_sel // Rom 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
模块说明:
RAM用于存放临时数据,可读可写。
Verilog代码:
// Description: RISC-CPU RAM模块
// -----------------------------------------------------------------------------
module ram (
input ena , // Enable
input read , // read Enable
input write , // write Enable
inout wire [7:0] data , // data
input [9:0] addr // address
);
reg [7:0] ram [10'h3ff:0] ;
assign data = (read && ena )? ram[addr]:8'h zz;
always@(posedge write) begin
ram[addr] <= data;
end
endmodule
模块说明:
RAM用于存放只读数据。
Verilog代码:
// Description: RISC-CPU ROM模块
// -----------------------------------------------------------------------------
module rom (
input [12:0] addr ,
input read ,
input ena ,
output wire [7:0] data
);
reg [7:0] memory [13'h1ff:0];
assign data = (read && ena)? memory[addr]:8'b zzzz_zzzz;
endmodule
模块图:

Verilog代码:
// Description: RISC-CPU 顶层模块
// -----------------------------------------------------------------------------
//`include "clk_gen.v"
//`include "accum.v"
//`include "adr.v"
//`include "alu.v"
//`include "machine.v"
//`include "counter.v"
//`include "machinectl.v"
//`iclude "machine.v"
//`include "register.v"
//`include "datactl.v"
module RISC_CPU (
input clk ,
input reset ,
output wire rd ,
output wire wr ,
output wire halt ,
output wire fetch ,
//addr
output wire [12:0] addr ,
output wire [12:0] ir_addr ,
output wire [12:0] pc_addr ,
inout wire [7:0] data ,
//op
output wire [2:0] opcode
);
wire [7:0] alu_out ;
wire [7:0] accum ;
wire zero ;
wire inc_pc ;
wire load_acc ;
wire load_pc ;
wire load_ir ;
wire data_ena ;
wire contr_ena ;
wire alu_ena ;
//inst
clk_gen mclk_gen(
.clk (clk ),
.reset (reset ),
.fetch (fetch ),
.alu_ena (alu_ena )
);
register m_register(
.data (data ),
.ena (load_ir ),
.rst (reset ),
.clk (clk ),
.opc_iraddr ({opcode,ir_addr} )
);
accum m_accum(
.data (alu_out ),
.ena (load_acc ),
.clk (clk ),
.rst (reset ),
.accum (accum )
);
alu m_alu(
.data (data ),
.accum (accum ),
.clk (clk ),
.alu_ena (alu_ena ),
.opcode (opcode ),
.alu_out (alu_out ),
.zero (zero )
);
machinectl m_machinectl(
.clk (clk ),
.rst (reset ),
.fetch (fetch ),
.ena (contr_ena )
);
machine m_machine(
.inc_pc (inc_pc ),
.load_acc (load_acc ),
.load_pc (load_pc ),
.rd (rd ),
.wr (wr ),
.load_ir (load_ir ),
.clk (clk ),
.datactl_ena(data_ena ),
.halt (halt ),
.zero (zero ),
.ena (contr_ena ),
.opcode (opcode )
);
datactl m_datactl(
.in (alu_out ),
.data_ena (data_ena ),
.data (data )
);
adr m_adr(
.fetch (fetch ),
.ir_addr (ir_addr ),
.pc_addr (pc_addr ),
.addr (addr )
);
counter m_counter(
.clock (inc_pc ),
.rst (reset ),
.ir_addr (ir_addr ),
.load (load_pc ),
.pc_addr (pc_addr )
);
endmodule
Testbench包含三个测试程序,这个部分不能综合。
TEST1程序用于验证RISC-CPU的逻辑功能,根据汇编语言由人工编译的。
若各条指令正确,应该在地址2E(hex)处,在执行HLT时刻停止。若程序在任何其他位置停止,则必有一条指令运行错误,可以按照注释找到错误的指令。
test1汇编程序:
//机器码
@00
//address statement
111_0000 //00 BEGIN: JMP TST_JMP
0011_1100
000_0000 //02 HLT //JMP did not work
0000_0000
000_00000 //04 HLT //JMP did not load PC skiped
0000_0000
101_1100 //06 JMP_OK: LDA DATA
0000_0000
001_00000 //08 SKZ
0000_0000
000_0000 //0a HLT
0000_0000
101_11000 //0C LDA DATA_2
0000_0001
001_00000 //0E SKZ
0000_0000
111_0000 //10 JMP SKZ_OK
001_0100
000_0000 //12 HLT
0000_0000
110_11000 //14 SKZ_OK: STO TEMP
0000_0010
101_11000 //16 LDA DATA_1
0000_0000
110_11000 //18 STO TEMP
0000_0010
101_11000 //1A LDA TEMP
0000_0010
001_00000 //1C SKZ
0000_0000
000_00000 //1E HLT
0000_0000
100_11000 //20 XOR DATA_2
0000_0001
001_00000 //22 SKZ
0000_0000
111_00000 //24 JMP XOR_OK
0010_1000
000_00000 //26 HLT
0000_0000
100_11000 //28 XOR_OK XOR DATA_2
0000_0001
001_00000 //2A SKZ
0000_0000
000_00000 //2C HLT
0000_0000
000_0000 //2E END
0000_0000
111_00000 //30 JMP BEGIN
0000_0000
@3c
111_00000 //3c TST_JMP IMR OK
0000_0110
000_00000 //3E HLT
test1数据文件:
/-----------------------------------
@00 ///address statement at RAM
00000000 //1800 DATA_1
11111111 //1801 DATA_2
10101010 //1082 TEMP
TEST1程序用于验证RISC-CPU的逻辑功能,根据汇编语言由人工编译的。
这个程序是用来测试RISC-CPU的高级指令集,若执行正确,应在地址20(hex)处在执行HLT时停止。
test2汇编程序:
@00
101_11000 //00 BEGIN
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
0000_0000
010_11000 //0A ADD DATA_1
0000_0000
001_00000 //0C SKZ
0000_0000
111_00000 //0E JMP ADD_OK
0001_0010
111_00000 //10 HLT
0000_0000
100_11000 //12 ADD_OK XOR DATA_3
0000_0010
010_11000 //14 ADD DATA_1
0000_0000
110_11000 //16 STO TEMP
0000_0011
101_11000 //18 LDA DATA_1
0000_0000
010_11000 //1A ADD TEMP
0000_0001
001_00000 //1C SKZ
0000_0000
000_00000 //1E HLT
0000_0000
000_00000 //END HLT
0000_0000
111_00000 //JMP BEGIN
0000_0000
test2数据文件:
@00
00000001 //1800 DATA_1
10101010 //1801 DATA_2
11111111 //1802 DATA_3
00000000 //1803 TEMP
TEST3程序是一个计算0~144的斐波那契数列的程序,用来验证CPU整体功能。
test3汇编程序:
@00
101_11000 //00 LOOP:LDA FN2
0000_0001
110_11000 //02 STO TEMP
0000_0010
010_11000 //04 ADD FN1
0000_0000
110_11000 //06 STO FN2
0000_0001
101_11000 //08 VLDA 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
test3数据文件:
@00
00000001 //1800 FN1
00000000 //1801 FN2
00000000 //1802 TEMP
10010000 //1803 LIMIT
Verilog代码:
// Description: RISC-CPU 测试程序
// -----------------------------------------------------------------------------
`include "RISC_CPU.v"
`include "ram.v"
`include "rom.v"
`include "addr_decode.v"
`timescale 1ns/1ns
`define PERIOD 100 // matches clk_gen.v
module cputop;
reg [( 3 * 8 ): 0 ] mnemonic; // array that holds 3 8 bits ASCII characters
reg [ 12 : 0 ] PC_addr, IR_addr;
reg reset_req, clock;
wire [ 12 : 0 ] ir_addr, pc_addr; // for post simulation.
wire [ 12 : 0 ] addr;
wire [ 7 : 0 ] data;
wire [ 2 : 0 ] opcode; // for post simulation.
wire fetch; // for post simulation.
wire rd, wr, halt, ram_sel, rom_sel;
integer test;
//-----------------DIGITAL LOGIC----------------------
cpu t_cpu (.clk( clock ),.reset( reset_req ),.halt( halt ),.rd( rd ),.wr( wr ),.addr( addr ),.data( data ),.opcode( opcode ),.fetch( fetch ),.ir_addr( ir_addr ),.pc_addr( pc_addr ));
ram t_ram (.addr ( addr [ 9 : 0 ]),.read ( rd ),.write ( wr ),.ena ( ram_sel ),.data ( data ));
rom t_rom (.addr ( addr ),.read ( rd ), .ena ( rom_sel ),.data ( data ));
addr_decoder t_addr_decoder (.addr( addr ),.ram_sel( ram_sel ),.rom_sel( rom_sel ));
//-------------------SIMULATION-------------------------
initial begin
clock = 0;
// display time in nanoseconds
$timeformat ( -9, 1, "ns", 12 );
display_debug_message;
sys_reset;
test1; $stop;
test2; $stop;
test3;
$finish; // simulation is finished here.
end // initial
task display_debug_message;
begin
$display ("\n************************************************" );
$display ( "* THE FOLLOWING DEBUG TASK ARE AVAILABLE: *" );
$display ( "* \"test1;\" to load the 1st diagnostic program. *");
$display ( "* \"test2;\" to load the 2nd diagnostic program. *");
$display ( "* \"test3;\" to load the Fibonacci program. *");
$display ( "************************************************\n");
end
endtask // display_debug_message
task test1;
begin
test = 0;
disable MONITOR;
$readmemb ("test1.pro", t_rom.memory );
$display ("rom loaded successfully!");
$readmemb ("test1.dat", t_ram.ram );
$display ("ram loaded successfully!");
#1 test = 1;
#14800;
sys_reset;
end
endtask // test1
task test2;
begin
test = 0;
disable MONITOR;
$readmemb ("test2.pro", t_rom.memory );
$display ("rom loaded successfully!");
$readmemb ("test2.dat", t_ram.ram );
$display ("ram loaded successfully!");
#1 test = 2;
#11600;
sys_reset;
end
endtask // test2
task test3;
begin
test = 0;
disable MONITOR;
$readmemb ("test3.pro", t_rom.memory );
$display ("rom loaded successfully!");
$readmemb ("test3.dat", t_ram.ram );
$display ("ram loaded successfully!");
#1 test = 3;
#94000;
sys_reset;
end
endtask // test1
task sys_reset;
begin
reset_req = 0;
#( `PERIOD * 0.7 ) reset_req = 1;
#( 1.5 * `PERIOD ) reset_req = 0;
end
endtask // sys_reset
//--------------------------MONITOR--------------------------------
always@( test ) begin: MONITOR
case( test )
1: begin // display results when running test 1
$display("\n*** RUNNING CPU test 1 - The Basic CPU Diagnostic Program ***");
$display("\n TIME PC INSTR ADDR DATA ");
$display(" ------ ---- ------- ------ ------ ");
while( test == 1 )@( t_cpu.pc_addr ) begin // fixed
if(( t_cpu.pc_addr % 2 == 1 )&&( t_cpu.fetch == 1 )) begin // fixed
#60 PC_addr <= t_cpu.pc_addr - 1;
IR_addr <= t_cpu.ir_addr;
#340 $strobe("%t %h %s %h %h", $time, PC_addr, mnemonic, IR_addr, data ); // Here data has been changed t_cpu.m_register.data
end // if t_cpu.pc_addr % 2 == 1 && t_cpu.fetch == 1
end // while test == 1 @ t_cpu.pc_addr
end
2: begin // display results when running test 2
$display("\n*** RUNNING CPU test 2 - The Basic CPU Diagnostic Program ***");
$display("\n TIME PC INSTR ADDR DATA ");
$display(" ------ ---- ------- ------ ------ ");
while( test == 2 )@( t_cpu.pc_addr ) begin // fixed
if(( t_cpu.pc_addr % 2 == 1 )&&( t_cpu.fetch == 1 )) begin // fixed
#60 PC_addr <= t_cpu.pc_addr - 1;
IR_addr <= t_cpu.ir_addr;
#340 $strobe("%t %h %s %h %h", $time, PC_addr, mnemonic, IR_addr, data ); // Here data has been changed t_cpu.m_register.data
end // if t_cpu.pc_addr % 2 == 1 && t_cpu.fetch == 1
end // while test == 2 @ t_cpu.pc_addr
end
3: begin // display results when running test 3
$display("\n*** RUNNING CPU test 3 - An Executable Program **************");
$display("***** This program should calculate the fibonacci *************");
$display("\n TIME FIBONACCI NUMBER ");
$display(" ------ -----------------_ ");
while( test == 3 ) begin
wait( t_cpu.opcode == 3'h 1 ) // display Fib. No. at end of program loop
$strobe("%t %d", $time, t_ram.ram [ 10'h 2 ]);
wait( t_cpu.opcode != 3'h 1 );
end // while test == 3
end
endcase // test
end // MONITOR: always@ test
//-------------------------HALT-------------------------------
always@( posedge halt ) begin // STOP when HALT intruction decoded
#500 $display("\n******************************************");
$display( "** A HALT INSTRUCTION WAS PROCESSED !!! **");
$display( "******************************************");
end // always@ posedge halt
//-----------------------CLOCK & MNEMONIC-------------------------
always#(`PERIOD / 2 ) clock = ~ clock;
always@( t_cpu.opcode ) begin // get an ASCII mnemonic for each opcode
case( t_cpu.opcode )
3'b 000 : mnemonic = "HLT";
3'b 001 : mnemonic = "SKZ";
3'b 010 : mnemonic = "ADD";
3'b 011 : mnemonic = "AND";
3'b 100 : mnemonic = "XOR";
3'b 101 : mnemonic = "LDA";
3'b 110 : mnemonic = "STO";
3'b 111 : mnemonic = "JMP";
default : mnemonic = "???";
endcase
end
endmodule
对所有代码进行编译仿真

test1程序仿真结果

test2程序仿真结果

test3程序仿真结果

使用Quartus (Quartus Prime 17.1) Standard Edition对RTL进行综合,对综合后的资源占用和电路图进行检查。
RTL图

FSM图

chip plan图
蓝色为占用部分

资源占用

至此,整个练手项目完成,从完成度和难度来讲,这个小项目更加偏向于教学练习,CPU也是数字IC的重要研究方向,对此感兴趣的同学可以找点论文和开源资料进行学习。之所以把这个项目放到第一来讲,是因为不要小瞧这个项目,虽然看上去简单,但是对工程文件的管理以及项目实践的习惯非常重要,希望大家都能培养一个良好的工程习惯,书本上的代码也有一点问题,这里贴上的并不是最优解,只是带着大家走了一个简单的流程,最后综合的工具也是FPGA相关的,并没有使用DC等数字IC专业的EDA软件,后续有时间会把这个地方进行补齐。
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我正在尝试解析一个CSV文件并使用SQL命令自动为其创建一个表。CSV中的第一行给出了列标题。但我需要推断每个列的类型。Ruby中是否有任何函数可以找到每个字段中内容的类型。例如,CSV行:"12012","Test","1233.22","12:21:22","10/10/2009"应该产生像这样的类型['integer','string','float','time','date']谢谢! 最佳答案 require'time'defto_something(str)if(num=Integer(str)rescueFloat(s
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非
我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来
我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion
在Ruby中,是否有一种简单的方法可以将n维数组中的每个元素乘以一个数字?这样:[1,2,3,4,5].multiplied_by2==[2,4,6,8,10]和[[1,2,3],[1,2,3]].multiplied_by2==[[2,4,6],[2,4,6]]?(很明显,我编写了multiplied_by函数以区别于*,它似乎连接了数组的多个副本,不幸的是这不是我需要的)。谢谢! 最佳答案 它的长格式等价物是:[1,2,3,4,5].collect{|n|n*2}其实并没有那么复杂。你总是可以使你的multiply_by方法:c
我正在使用Ruby解决一些ProjectEuler问题,特别是这里我要讨论的问题25(Fibonacci数列中包含1000位数字的第一项的索引是多少?)。起初,我使用的是Ruby2.2.3,我将问题编码为:number=3a=1b=2whileb.to_s.length但后来我发现2.4.2版本有一个名为digits的方法,这正是我需要的。我转换为代码:whileb.digits.length当我比较这两种方法时,digits慢得多。时间./025/problem025.rb0.13s用户0.02s系统80%cpu0.190总计./025/problem025.rb2.19s用户0.0
我正在构建一个小部件来显示奥运会的奖牌数。我有一个“国家”对象的集合,其中每个对象都有一个“名称”属性,以及奖牌计数的“金”、“银”、“铜”。列表应该排序:1.首先是奖牌总数2.如果奖牌相同,按类型分割(金>银>铜,即2金>1金+1银)3.如果奖牌和类型相同,则按字母顺序子排序我正在用ruby做这件事,但我想语言并不重要。我确实找到了一个解决方案,但如果感觉必须有更优雅的方法来实现它。这是我做的:使用加权奖牌总数创建一个虚拟属性。因此,如果他们有2个金牌和1个银牌,加权总数将为“3.020100”。1金1银1铜为“3.010101”由于我们希望将奖牌数排序为最高的,因此列表按降序排
我想为名字验证编写一个正则表达式。正则表达式应包括所有字母(拉丁/法语/德语字符等)。但是我想从中排除数字并允许-。所以基本上它是\w(减)数(加)-。请帮忙。 最佳答案 ^[\p{L}-]+$\p{L}匹配anykindofletterfromanylanguage. 关于ruby-on-rails-rails中的正则表达式匹配[\w]和"-"但不匹配数字,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.c