第十七章 简化的 RISC CPU设计
前言:
在前面的各章中我们已经学习了VerilogHDL的基本语法、简单组合逻辑和简单时序逻辑模块的编写、Top-Down设计方法、还学习了可综合风格的组合逻辑和有限状态机的设计,其中EEPROM读写器的设计,可以算是一个较复杂的嵌套的有限状态机的设计,它是根据我们已完成的实际工程项目,为达到教学目标而改写的,已很接近真实的设计。在本章中,我们将介绍另一个经过简化的用于教学目标的精简指令集(RISC)CPU的构造原理和设计方法。作者相信读者参考书上的程序和解释,经过自己的努力,就可以独立完成该CPU核的设计和验证,从而学习Verilog设计方法,并由此逐步掌握这种利用硬件描述语言的高层次设计方法。
17.1 课题的由来和设计环境介绍:
在本章中,我们将通过自己动脑筋,设计出CPU的软核和固核。这个CPU是一个简化的专门为教学目的而设计的RISC_CPU。 在设计中我们不但关心 CPU 总体设计的合理性, 而且还使得构成这个RISC_CPU的每一个模块不仅是可仿真的也都可以综合成门级网表。因而从物理意义上说,这也是一个能真正通过具体电路结构而实现的CPU。为了能在这个虚拟的CPU上运行较为复杂的程序并进行仿真, 我们把寻址空间规定为8K(即13位地址线)字节。
下面让我们一步一步地来设计这样一个CPU,并进行RTL仿真、经过综合、布局布线后,再次进行一次仿真,从中我们可以体会到这种设计方法的潜力。本章中的VerilogHDL程序都是我们自己为教学目的而编写的,全部程序在CADENCE公司的NC-Verilog 环境、Synopsys VCS、 Mentor 公司的ModelSim 6.1 等环境下用Verilog语言进行了仿真。同时我们分别用Synplify、Altera Quartus II 等工具,针对不同的FPGA进行了综合。 顺利地通过RTL级仿真、综合后门级逻辑网表仿真以及布线后的门级结构电路模型仿真。这个 CPU 模型只是一个教学模型,设计也不一定很合理,只是从原理上说明了简单的RISC _CPU是如何构成的。本章的内容是想达到以下四个目的:1)学习RISC CPU的基本结构和原理;2)了解Verilog HDL仿真和综合工具的潜力;2)展示Verilog设计方法对软/硬件联合设计和验证的意义;3)学习并掌握一些常用的Verilog语法和验证方法。作者也希望本章的内容能引起对 CPU和复杂数字逻辑系统设计有兴趣的电子工程师们的注意,加入我国集成电路的设计队伍,提高我国电子产品的档次。由于作者的经验与学识有限,不足之处敬请读者批评、指正。
17.2.什么是CPU?
CPU 即中央处理单元的英文缩写,它是计算机的核心部件。计算机进行信息处理可分为两个步骤:
1) 将数据和程序(即指令序列)输入到计算机的存储器中。
2) 从第一条指令的地址起开始执行该程序,得到所需结果,结束运行。CPU的作用是协
调并控制计算机的各个部件执行程序,使其有条不紊地进行。因此它必须具有以下基本功能:
a)取指令:当程序已在存储器中时,首先根据程序入口地址取出一条程序,为此要发出指令地址及控制信号。
b)分析指令:即指令译码。是对当前取得的指令进行分析,指出它要求什么操作,并产生相应的操作命令。
c)执行指令:根据分析指令时产生的操作命令形成相应的操作控制信号序列,通过运算器,存储器及输入/输出设备的执行,实现每条指令的功能,其中包括对运算结果的处理以及下条指令地址的形成。
将其功能进一步细化,可概括如下:
1) 能对指令进行译码并执行规定的动作; 2) 可以进行算术和逻辑运算; 3) 能与存储器,外设交换数据;
0
4) 提供整个系统所需要的控制;
尽管各种CPU的性能指标和结构细节各不相同,但它们所能完成的基本功能相同。由功能分析,可知任何一种CPU内部结构至少应包含下面这些部件:
1)算术逻辑运算部件(ALU), 2)累加器,
3)程序计数器,
4)指令寄存器,译码器, 5)时序和控制部件。
RISC 即精简指令集计算机(Reduced Instruction Set Computer)的缩写。它是一种八十年代出现的CPU,与一般的CPU 相比不仅只是简化了指令系统,而且是通过简化指令系统使计算机的结构更加简单合理,从而提高了运算速度。从实现的途径看,RISC_CPU与一般的CPU的不同处在于:它的时序控制信号形成部件是用硬布线逻辑实现的而不是采用微程序控制的方式。所谓硬布线逻辑也就是用触发器和逻辑门直接连线所构成的状态机和组合逻辑,故产生控制序列的速度比用微程序控制方式快得多,因为这样做省去了读取微指令的时间。RISC_CPU也包括上述这些部件,下面就详细介绍一个简化的用于教学目的的RISC_CPU的可综合VerilogHDL模型的设计和仿真过程。
17.3. RISC CPU结构
RISC_CPU是一个复杂的数字逻辑电路,但是它的基本部件的逻辑并不复杂。我们可把它分成八个基本部件来考虑:
1)时钟发生器 2)指令寄存器 3)累加器
4)算术逻辑运算单元 5)数据控制器 6)状态控制器 7)程序计数器 8)地址多路器
各部件的相互连接关系见图17.1。其中时钟发生器利用外来时钟信号进行分频生成一系列时钟信号,送往其他部件用作时钟信号。各部件之间的相互操作关系则由状态控制器来控制。各部件的具体结构和逻辑关系在下面的小节里逐一进行介绍。
1
DATA[7:0] RST CLK FETCH ALU_ENCLK CLKA CLKGEN INSTRUCTION REGISTER RST REGISTER CLK opc_iraddr[15:0] DATA[ 7: 0]OPC_IRADDRS[ 15:0] ENA
OPCODE [2:0] IR_ADDR[12:0] ALU_OUT[7:0] DATA[7:0] ACCUM[7:0] ENA ACCUM CLK RST ACCUM[7:0] DATA[7:0] ALU_OUT[7:0] ACCUM[7:0]ZERO OPCODE[2:0] OPCODE[2:0] ZERO ALU CLK INC_PC LOAD_ACC ZERO LOAD_PC FETCH CONTROL RST RD (MACHINECTL MACHINE) WR OPCODE[2:0] LOAD_IR HALT DATACTL_ENA LOAD_ACC RD WR LOAD_IR HALT DATA_ENA DATA[7:0] IN[7:0] DATACTL DATA_ENA INC_PC LOAD_PC DATA[7:0] IR_ADDR[2:0] ADDR[12:0] FETCH IR_ADDR[12:0] ADR PC_ADDR[12:0] PC_ADDR[12:0] IR_ADDR[12:0] PC_ADDR[12:0] LOAD CLOCK RST ADDR[12:0] COUNTER 图 17.1 RISC-CPU中各部件的相互连接关系
2
17.3.1时钟发生器
CLK
CLKGEN CLK CLK ALU_ENA ALU_ENA CLK
RESET RESET FETCH FETCH
图1. 时钟发生器
时钟发生器 clkgen 利用外来时钟信号clk 来生成一系列时钟和控制信号:clk、fetch、alu_ena 送往CPU的其他部件。其中fetch是控制信号,clk 的八分频信号,当fetch高电平时,使clk能触发CPU控制器开始执行一条指令,同时fetch信号还将控制地址多路器输出指令地址和数据地址。clk信号用作指令寄存器、累加器、状态控制器的时钟信号。alu_ena 则用于控制算术逻辑运算单元的操作。时钟发生器clkgen的波形见下图2所示:
clk
fetch
alu_ena
图2 时钟发生器clkgen的波形
其VerilogHDL 程序见下面的模块:
//----------------------------------------- clk_gen.v 的开始 ---------------------------- `timescale 1ns/1ns
module clk_gen (clk,reset,fetch,alu_ena); input clk, reset; output fetch,alu_ena; wire clk,reset;
reg fetch,alu_ena; reg[7:0] state;
parameter S1 = 8'b00000001, S2 = 8'b00000010, S3 = 8'b00000100, S4 = 8'b00001000, S5 = 8'b00010000, S6 = 8'b00100000, S7 = 8'b01000000, S8 = 8'b10000000,
idle = 8'b00000000;
always @(posedge clk) if(reset)
begin
fetch <= 0; alu_ena <= 0; state <= idle; end else
3
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 endmodule
//----------------------------------------- clk_gen.v 的结束 ----------------------------
由于在时钟发生器的设计中采用了同步状态机的设计方法,不但使clk_gen模块的源程序可以被各种综合器综合,也使得由其生成的fetch、alu_ena 在同步性能上有明显的提高,为整个系统的性能提高打下了良好的基础。
4
17.3.2 指令寄存器
INSTRUCTION REGISTER
DATA[7:0] OPCODE[2:0] DATA[7:0]
LOAD_IR
opc_iraddr[15:0] ENA
CLK CLK REGISTER IR_ADDR[12:0] RESET RST 图3 指令寄存器模块
顾名思义,指令寄存器用于寄存指令。
指令寄存器的触发时钟是clk,在clk的正沿触发下,寄存器将数据总线送来的指令存入高8位或低8位寄存器中。但并不是每个clk的上升沿都寄存数据总线的数据,因为数据总线上有时传输指令,有时传输数据。什么时候寄存,什么时候不寄存由CPU状态控制器的load_ir信号控制。load_ir信号通过ena 口输入到指令寄存器。复位后,指令寄存器被清为零。
每条指令为2个字节,即16位。高3位是操作码,低13位是地址。(CPU的地址总线为13位,寻址空间为8K字节。)本设计的数据总线为8位,所以每条指令需取两次。先取高8位,后取低8位。而当前取的是高8位还是低8位,由变量state记录。state为零表示取的高8位,存入高8位寄存器,同时将变量state置为1。下次再寄存时,由于state为1,可知取的是低8位,存入低8位寄存器中。
其VerilogHDL 程序见下面的模块:
//-------------------------------------------------------------------- `timescale 1ns/1ns
module register(opc_iraddr,data,ena,clk,rst); output [15:0] opc_iraddr; input [7:0] data; input ena, clk, rst; reg [15:0] opc_iraddr; reg state;
always @(posedge clk)
begin if(rst) begin
opc_iraddr <= 16'b0000_0000_0000_0000;
state <=1'b0;
end else begin if(ena) //如果加载指令寄存器信号load_ir到来, begin //分两个时钟每次8位加载指令寄存器 casex(state) //先高字节,后低字节
5
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'bxxxxxxxxxxxxxxxx;
state<=1'bx;
end
endcase end else
state<=1'b0; end
end
endmodule
//--------------------------------------------------------------------
17.3.3.累加器
ACCUMULATOR ALU_OUT[7:0] DATA[7:0] ACCUM[7:0]
LOAD_ACC ACCUM[7:0] ENA
CLK CLK ACCUM
RST
RST 图4 累加器模块
累加器用于存放当前的结果,它也是双目运算其中一个数据来源。复位后,累加器的值是零。当累加器通过ena口收到来自CPU状态控制器load_acc信号时,在clk时钟正跳沿时就收到来自于数据总线的数据。
其VerilogHDL 程序见下面的模块:
//--------------------------------------------------------------
module accum( accum, data, ena, clk, rst); output[7:0]accum; input[7:0]data; input ena, clk, rst; reg[7:0]accum;
always@(posedge clk) begin if(rst)
accum <= 8'b0000_0000; //Reset else
if(ena) //当CPU状态控制器发出load_acc信号 accum <= data; //Accumulate
end
6
endmodule
//--------------------------------------------------------------
17.3.4.算术运算器
DATA[7:0] ZERO DATA[7:0] ZERO
ALU_OUT[7:0] ALU_ ENA ALU_OUT[7:0] ALU_ENA ALU ACCUM[7:0] ACCUM[7:0]
CLK OPCODE[2:0]
C L K OPCODE[2:0]
图5 算术运算器模块
算术逻辑运算单元 根据输入的8种不同操作码分别实现相应的加、与、异或、跳转等8种基本操作运算。利用这几种基本运算可以实现很多种其它运算以及逻辑判断等操作。
其VerilogHDL 程序见下面的模块:
//------------------------------------------------------------------------------ `timescale 1ns/1ns
module alu (alu_out, zero, data, accum, clk, alu_ena, opcode); output [7:0]alu_out; output zero;
input [7:0] data, accum; input [2:0] opcode ; input alu_ena; input clk;
reg [7:0] alu_out;
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;
assign zero = !accum; always @(posedge clk) if (alu_ena)
begin //操作码来自指令寄存器的输出opc_iaddr<15..0>的低3位 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;
7
JMP: alu_out <= accum; default: alu_out <= 8'bxxxx_xxxx; endcase end endmodule
//------------------------------------------------------------------------------------
17.3.5.数据控制器
DATACTL
ALU_OUT[7:0]
IN[7:0] DATA[7:0] DATA[7:0] DATACTL_ENA DATA_ENA
图6 数据控制器模块
数据控制器的作用是控制累加器数据输出,由于数据总线是各种操作时传送数据的公共通道,不同的情况下传送不同的内容。有时要传输指令,有时要传送RAM区或接口的数据。累加器的数据只有在需要往RAM区或端口写时才允许输出,否则应呈现高阻态,以允许其它部件使用数据总线。 所以任何部件往总线上输出数据时,都需要一控制信号。而此控制信号的启、停,则由CPU状态控制器输出的各信号控制决定。数据控制器何时输出累加器的数据则由状态控制器输出的控制信号datactl_ena决定。
其VerilogHDL 程序见下面的模块:
//--------------------------------------------------------------------
module datactl (data, in, data_ena);
output [7:0] data; input [7:0] in; input data_ena;
assign data = (data_ena)? in : 8'bzzzz_zzzz;
endmodule
//--------------------------------------------------------------------
17.3.6.地址多路器
ADR PC_ADDR[12 : 0]
PC_ADDR[12 : 0] ADDR[12 : 0] ADDR[12 : 0]
IR_ADDR[12 : 0] IR_ADDR[12 : 0]
FETCH
FETCH 图7 地址多路器模块
8
地址多路器用于选择输出的地址是PC(程序计数)地址还是数据/端口地址。每个指令周期的前4个时钟周期用于从ROM中读取指令,输出的应是PC地址。后4个时钟周期用于对RAM或端口的读写,该地址由指令中给出。地址的选择输出信号由时钟信号的8分频信号fetch提供。
其VerilogHDL 程序见下面的模块:
//------------------------------------------------------------------------------
module adr(addr, fetch, ir_addr, pc_addr); output [12:0] addr;
input [12:0] ir_addr, pc_addr; input fetch;
assign addr = fetch? pc_addr : ir_addr;
endmodule
//------------------------------------------------------------------------------
17.3.7.程序计数器
程序计数器用于提供指令地址。以便读取指令,指令按地址顺序存放在存储器中。有两种途径可形成指令地址:其一是顺序执行的情况,其二是遇到要改变顺序执行程序的情况,例如执行JMP指令后,需要形成新的指令地址。下面就来详细说明PC地址是如何建立的。 COUNTER IR_ADDR[12 : 0] IR_ADDR[12 : 0] LOAD_PCLOAD PC_ADDR[12 : 0]
PC_ADDR[12 : 0] CLOCK INC_PC
RESET
RST
图8 程序计数器模块
复位后,指令指针为零,即每次CPU重新启动将从ROM的零地址开始读取指令并执行。每条指令执行完需2个时钟,这时pc_addr已被增2,指向下一条指令。(因为每条指令占两个字节。)如果正执行的指令是跳转语句,这时CPU状态控制器将会输出load_pc信号,通过load口进入程序计数器。程序计数器(pc_addr)将装入目标地址(ir_addr),而不是增2。
其VerilogHDL 程序见下面的模块:
//------------------------------------------------------------------------------
module counter (clock, rst, ir_addr, load, pc_addr); output [12:0] pc_addr; input [12:0] ir_addr; input load, clock, rst; reg [12:0] pc_addr;
always @( posedge clock or posedge rst ) begin
if(rst)
pc_addr <= 13'b0_0000_0000_0000;
9
else
if(load)
pc_addr <= ir_addr; else
pc_addr <= pc_addr + 1;
end endmodule
//------------------------------------------------------------------------------
17.3.8.状态控制器
CLK INC_PC CLK INC_PC
LOA D_ACC LOAD_ACC CLK FETCH LOAD_PC FETCH LOAD_PC m achinectl MEM_RD
ENAENARDRST MACHINE
RST MEM_WR WR
ZERO ZERO LOAD_IR LOAD_IR HALT OPCODE[2:0] OPCODE[2:0] HALT DATACTL_ENA DATACTL_ENA
图9 状态控制器模块
状态控制器由两部分组成:
状态机(上图中的MACHINE部分)
状态控制器(上图中的MACHINECTL部分)
状态机控制器接受复位信号RST,当RST有效时通过信号ena使其为0,输入到状态机中停止状态机的工作。
状态控制器的VerilogHDL程序见下面模块: //------------------------------------------ `timescale 1ns/1ns
module machinectl( clk, rst, fetch, ena); input clk, rst,fetch; output ena;
reg ena; reg state;
always @(posedge clk ) begin if(rst) begin
10
ena<=0; end else
if (fetch) begin
ena <= 1; end end
endmodule
//------------------------------------------
状态机是CPU的控制核心,用于产生一系列的控制信号,启动或停止某些部件。CPU何时进行读指令读写I/O端口,RAM区等操作,都是由状态机来控制的。状态机的当前状态,由变量state记录,state的值就是当前这个指令周期中已经过的时钟数(从零计起)。
指令周期是由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外(指向下一条指令),其它各控制线输出为零。
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无
变化。
状态机的VerilogHDL 程序见下面模块:
//------------------------------------------------------------------------------------ `timescale 1ns/1ns
module machine ( inc_pc, load_acc, load_pc, rd,wr, load_ir,
datactl_ena, halt, clk, zero, ena, opcode );
output inc_pc, load_acc, load_pc, rd, wr, load_ir; output datactl_ena, halt; input clk, zero, ena; input [2:0] opcode;
reg inc_pc, load_acc, load_pc, rd, wr, load_ir; reg datactl_ena, halt; reg [2:0] state;
11
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 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
//-----------------begin of 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
12
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) 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;
13
end
state <= 3'b110; end
3'b110: //idle
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
//-----------------end of task ctl_cycle---------
endmodule
//------------------------------------------------------------------------------
状态机和状态机控制器组成了状态控制器。它们之间的连接关系很简单。见本小节的图9。
17.3.9.外围模块
14
为了对RISC_CPU进行测试,需要有存储测试程序的ROM和装载数据的RAM、地址译码器。下面来简单介绍一下:
1)地址译码器:
module addr_decode( addr, rom_sel, ram_sel); output rom_sel, ram_sel; input [12:0] addr; 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
地址译码器用于产生选通信号,选通ROM或RAM。
1FFFH---1800H RAM 17FFH---0000H ROM
2)RAM和ROM
module ram( data, addr, ena, read, write ); inout [7:0] data; input [9:0] addr; input ena;
input read, write;
reg [7:0] ram [10'h3ff:0];
assign data = ( read && ena )? ram[addr] : 8'hzz;
always @(posedge write) begin
ram[addr]<= data; end endmodule
module rom( data, addr, read, ena ); output [7:0] data; input [12:0] addr; input read, ena;
reg [7:0] memory [13'h1fff:0]; wire [7:0] data;
assign data= ( read && ena )? memory[addr] : 8'bzzzzzzzz;
endmodule
ROM用于装载测试程序,可读不可写。RAM用于存放数据,可读可写。
15
17.4. RISC_CPU 操作和时序
一个微机系统为了完成自身的功能,需要CPU执行许多操作。以下是RISC_CPU的主要操作:
1)系统的复位和启动操作 2)总线读操作 3)总线写操作
下面详细介绍一下每个操作:
17.4.1.系统的复位和启动操作
RISC_CPU的复位和启动操作是通过reset引脚的信号触发执行的。当reset信号一进入高电平,RISC_CPU就会结束现行操作,并且只要reset停留在高电平状态,CPU就维持在复位状态。在复位状态,CPU各内部寄存器都被设为初值,全部为零。数据总线为高阻态,地址总线为0000H,所有控制信号均为无效状态。reset回到低电平后,接着到来的第一个fetch上升沿将启动RISC_CPU开始工作,从ROM的000处开始读取指令并执行相应操作。波形图见图10。虚线标志处为RISC_CPU启动工作的时刻。
图10. RISC_CPU的复位和启动操作波形
17.4.2.总线读操作
每个指令周期的前0--3个时钟周期用于读指令,在状态控制器一节中已详细讲述,这里就不再重复。第3.5个周期处,存储器或端口地址就输出到地址总线上,第4--6个时钟周期,读信号rd有效,数据送到数据总线上,以备累加器锁存,或参与算术、逻辑运算。第7个时钟周期,读信号无效,第7.5个周期,地址总线输出PC地址,为下一个指令做好准备。
16
图11. CPU从存储器或端口读取数据的时序
17.4.3写总线操作
每个指令周期的第3.5个时钟周期处,写的地址就建立了,第4个时钟周期输出数据,第5个时钟周期输出写信号。至第6个时钟结束,数据无效,第7.5时钟地址输出为PC地址,为下一个指令周期做好准备。
图12. CPU对存储器或端口写数据的时序
17.5.RISC_CPU寻址方式和指令系统
RISC_CPU的指令格式一律为:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 指令 地址
它的指令系统仅由8条指令组成。
1)HLT 停机操作。该操作将空一个指令周期,即8个时钟周期。
2)SKZ为零跳过下一条语句。该操作先判断当前alu中的结果是否为零,若是零就跳过下一条语句,否则继续执行。
3)ADD 相加。该操作将累加器中的值与地址所指的存储器或端口的数据相加,结果仍送回累加器中。
4)AND 相与。该操作将累加器的值与地址所指的存储器或端口的数据相与,结果仍送回累加器中。 5)XOR 异或。该操作将累加器的值与指令中给出地址的数据异或,结果仍送回累加器中。
17
6)LDA 读数据。该操作将指令中给出地址的数据放入累加器。 7)STO 写数据。该操作将累加器的数据放入指令中给出的地址。
8)JMP 无条件跳转语句。该操作将跳转至指令给出的目的地址,继续执行。
RISC_CPU是8位微处理器,一律采用直接寻址方式,即数据总是放在存储器中,寻址单元的地址由指令直接给出。这是最简单的寻址方式。
17.6. RISC_CPU模块的调试
17.6.1. RISC_CPU模块的前仿真
为了对所设计的RISC_CPU模型进行验证,需要把RISC_CPU包装在一个模块下,这样其内部连线就隐蔽起来,从系统的角度看就显得简洁,见图13,还需要建立一些必要的外围器件模型,例如储存程序用的ROM模型、储存数据用的RAM和地址译码器等。这些模型都可以用VerilogHDL描述,由于不需要综合成具体的电路,只要保证功能和接口信号正确就能用于仿真。也就是说,用虚拟器件来代替真实的器件对所设计的RISC_CPU模型进行验证,检查各条指令是否执行正确,与外围电路的数据交换是否正常。这种模块是很容易编写的,上面17.3.9节中的ROM和RAM模块就是简化的虚拟器件的例子,可在下面的仿真中来代替真实的器件,用于验证RISC_CPU模型是否能正确地运行装入ROM和RAM的程序。在RISC_CPU的电路图上加上这些外围电路把有关的电路接通,见图13;也可以用VerilogHDL模块调用的方法把这些外围电路的模块连接上,这跟用真实的电路器件调试情况很类似,
DATA[7:0]
CLK DATA[7:1] CLK ADDR[9:0] HALT DATA[7:0] ADDR[9:0] HALT RST RD RST read RD WR RISC_CPU write WR RAM ADDR[12:0] ADDR[12:0]
ena
ADDR[12:0] ADDR[12:0] DATA[7:0] ram_sel read ADDR_DECODE ROM ena rom_sel
图 13 RISC_CPU和它的外围电路
下面介绍的是在modelsim 6.1 下进行调试的仿真测试程序cputop.v。可用于对以上所设计的RISCCPU进行仿真测试,下面是前仿真的测试程序cputop.v。它的作用是按模块的要求执行仿真,并显示仿真的结果,测试模块cputop.v中的$display和$monitor等系统调用能在计算机的显示屏幕上显示部分测试结果,可以同时用波型观察器观察有关信号的波形。
//------------------------------------------- cputop.v 文件的开始 -----------------------------------------------------
/************************************************************************************** *** 模块功能:cputop 模块通过运行三个不同的汇编程序对cpu模块进行完整的逻辑测试 *** 和验证。仿真程序在加栽后会自动执行,运行的结果在人机交互的显示 *** 屏上显示。本模块是不可综合的行为模块,用于对CPU模块进行RTL级和
18
*** 门级逻辑功能的全面仿真验证。本测试模块也可以为布线后仿真,只需要将 *** 代码的第一句 `include \"cpu.v\" 改写为`include \"cpu.vo\" 即可。
**************************************************************************************/ //********************************************************************** `include \"cpu.v\" // 若改为 `include \"cpu.vo\" 便可做布线后仿真 `include \"ram.v\" `include \"rom.v\"
`include \"addr_decode.v\"
//*****************************
`timescale 1ns / 1ns
`define PERIOD 100 // matches clk_gen.v
module cputop;
reg reset_req,clock; integer test;
reg [(3*8):0] mnemonic; //array that holds 3 8-bit ASCII characters reg [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; //为布线后仿真做的专门添加的CPU内部信号线 wire fetch; //为布线后仿真做的专门添加的CPU内部信号线 wire [12:0] ir_addr, pc_addr; //为布线后仿真做的专门添加的CPU内部信号线
//------------------------ cpu 模块与地址译码器和ROM、RAM的连接部分--------------------------------------
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_decode t_addr_decode (.addr(addr),.ram_sel(ram_sel),.rom_sel(rom_sel));
//--------------------cpu 模块与地址译码器和ROM、RAM的连接部分结束---------------------------------- initial begin clock=1;
//display time in nanoseconds $timeformat ( -9, 1, \" ns\ display_debug_message; sys_reset; test1; $stop; test2; $stop; test3;
$finish; // simulation is finished here. end
task display_debug_message; begin
$display(\"\\n**************************************************\");
$display(\"* THE FOLLOWING DEBUG TASK ARE AVAILABLE: *\"); $display(\"* \\\"test1; \\\" to load the 1st diagnostic progran. *\");
19
$display(\"* \\\"test2; \\\" to load the 2nd diagnostic program. *\"); $display(\"* \\\"test3; \\\" to load the Fibonacci program. *\");
$display(\"*****************************************************\\n\"); end endtask task test1; begin
test = 0;
disable MONITOR;
$readmemb (\"test1.pro\ $display(\"rom loaded successfully!\"); $readmemb(\"test1.dat\ $display(\"ram loaded successfully!\"); #1 test = 1; #14800 ; sys_reset; end endtask
task test2; begin
test = 0;
disable MONITOR;
$readmemb(\"test2.pro\ $display (\"rom loaded successfully!\"); $readmemb(\"test2.dat\ $display (\"ram loaded successfully!\"); #1 test = 2; #11600; sys_reset; end endtask
task test3; begin
test = 0;
disable MONITOR;
$readmemb(\"test3.pro\ $display(\"rom loaded successfully!\"); $readmemb(\"test3.dat\ $display(\"ram loaded successfully!\"); #1 test = 3; #94000; sys_reset; end endtask
task sys_reset; begin
reset_req = 0;
#(`PERIOD*0.7) reset_req = 1; #(1.5*`PERIOD) reset_req = 0; end endtask
always @(test) begin: MONITOR case (test)
20
1: begin //display results when running test 1
$display(\"\\n*** RUNNING CPUtest1 - The Basic CPU Diagnostic Program ***\");
$display(\"\\n TIME PC INSTR ADDR DATA \"); $display(\" ---------- ---- ----- ----- ----- \"); while (test == 1)
@(t_cpu.pc_addr)//fixed
if ((t_cpu.pc_addr%2 == 1)&&(t_cpu.fetch == 1)) //fixed begin
# 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
end 2: begin $display(\"\\n*** RUNNING CPUtest2 - The Advanced CPU Diagnostic Program ***\");
$display(\"\\n TIME PC INSTR ADDR DATA \"); $display(\" ---------- ---- ----- ----- ------ \"); while (test == 2) @(t_cpu.pc_addr)
if ((t_cpu.pc_addr %2 == 1) && (t_cpu.fetch == 1)) begin
# 60 PC_addr <= t_cpu.pc_addr - 1 ; IR_addr <= t_cpu.ir_addr;
# 340 $strobe(\"%t %h %s %h %h\ mnemonic, IR_addr, data ); end
end
3: begin
$display(\"\\n*** RUNNING CPUtest3 - 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'h1) // display Fib. No. at end of program loop $strobe(\"%t %d\ wait ( t_cpu.opcode != 3'h1); end end endcase end
//-------------------------------------------------------------------------
always @(posedge halt) //STOP when HALT instruction decoded begin
#500
$display(\"\\n***********************************************************\"); $display( \"** A HALT INSTRUCTION WAS PROCESSED !!! **\");
$display( \"********************************************************\\n\"); end
always #(`PERIOD/2) clock=~clock; always @(t_cpu.opcode)
//get an ASCII mnemonic for each opcode
21
case(t_cpu.opcode)
3'b000 : mnemonic =\"HLT\"; 3'h1 : mnemonic = \"SKZ\"; 3'h2 : mnemonic = \"ADD\"; 3'h3 : mnemonic = \"AND\"; 3'h4 : mnemonic = \"XOR\"; 3'h5 : mnemonic = \"LDA\"; 3'h6 : mnemonic = \"STO\"; 3'h7 : mnemonic = \"JMP\"; default : mnemonic = \"???\"; endcase
endmodule
//------------------------------------------- cputop.v 文件的结束 -----------------------------------------------------
针对程序做如下说明:测试程序中用宏指令 `include \" 模块文件名\" 包含了rom.v ,ram.v 和 addrdecode.v三个外部模块。它们都是检测 RISC_CPU 时必不可少虚拟部件。分别代表RAM,ROM 和地址译码器。
对于RISC_CPU需要综合成电路的部分,则通过cpu.v程序将它组合成一个独立的cpu模块。具体程序如下:
//------------------------------------------- cpu.v 文件的开始 ------------------------------------------- /****************************************************************************** *** 模块功能:CPU 模块是本章阐述的RISC_ CPU的核心, 共由十个可综合模块的 *** 连接组成。它本身也是可综合的模块。已经过门级后仿真验证。 *****************************************************************************/ //------------------------------------------- cpu.v 文件的开始 ------------------------------------------- //*************************** `include \"clk_gen.v\" `include \"accum.v\" `include \"adr.v\" `include \"alu.v\"
`include \"machine.v\" `include \"counter.v\" `include \"machinectl.v\" `include \"register.v\" `include \"datactl.v\"
//*************************** `timescale 1ns/1ns
module cpu ( clk, reset, halt, rd, wr, addr, data, opcode, fetch, ir_addr, pc_addr ); input clk, reset; output rd, wr, halt; output [12:0] addr; output [2:0] opcode; output fetch;
output [12:0] ir_addr, pc_addr; inout [7:0] data; wire clk,reset,halt; wire [7:0] data; wire [12:0] addr; wire rd, wr;
wire clk1, fetch, alu_ena; wire [2:0] opcode;
wire [12:0] ir_addr, pc_addr; wire [7:0] alu_out, accum;
22
wire zero, inc_pc, load_acc, load_pc, load_ir, data_ena, control_ena;
clk_gen m_clk_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_machinecl(.clk(clk),.rst(reset),.fetch(fetch),.ena(control_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(control_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
//--------------------------------------- cpu.v 文件的结束 -------------------------------------------------
其中 control_ena 用于machinect1与machine之间的ena的连接。cputop.v 中用到下面两条语句需要解释一下:
$readmemb ( \"test1.pro\和 $readmemb ( \"test1.dat\
即可把编译好的汇编机器码装入虚拟ROM,把需要参加运算的数据装入虚拟RAM就可以开始仿真。上面语句中的第一项为打开的文件名,后一项为系统层次管理下的ROM模块和RAM模块中的存储器memory和ram。
下面清单所列出是用于测试RISC_CPU基本功能而分别装入虚拟ROM和RAM的机器码和数据文件,其文件名分别为test1.pro,test1.dat,test2.pro,test2.dat,test3.pro,test3.dat和调用这些测试程序进行仿真的程序cputop.v文件:
//-------------------------------------------- 文件 test1.pro ----------------------------------------- /******************************************************************************
*** Test1 程序是用于验证RISC_ CPU逻辑功能的机器代码。是根据汇编语言由人工编译的。 *** 本汇编程序用于测试RISC_ CPU的基本指令集,如果RISC_ CPU的各条指令执行正确, *** 它应在地址为2E(hex)处,在执行HLT时停止运行。 如果该程序在任何其他地址暂停 *** 运行,则必有一条指令运行出错。可参照注释找到出错的指令。
*** @符号后的十六进制数表示存储器的地址,以下的二进制数为机器码; *** 每行//符号后表示自己为RISC_CPU设计的汇编程序和程序注释。
23
*****************************************************************************/
//------------------------------- test1.pro开始 --------------------------------------------------------------------- //------- 机器码 地址 汇编助记符 注释
@00 //address statement
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 0000_0000
001_00000 0000_0000
000_00000 0000_0000
101_11000 0000_0001
001_00000 0000_0000
111_00000 0001_0100
000_00000 0000_0000
110_11000 0000_0010
101_11000 0000_0000
110_11000 0000_0010
101_11000 0000_0010
001_00000 0000_0000
000_00000 0000_0000
100_11000 0000_0001
001_00000 0000_0000
111_00000 0010_1000
000_00000 0000_0000
100_11000 0000_0001
001_00000 0000_0000
000_00000 0000_0000
000_00000 0000_0000
111_00000 0000_0000
@3c
// 06 // 08 // 0a // 0c // 0e // 10 // 12 // 14 // 16 // 18 // 1a // 1c // 1e // 20 // 22 // 24 // 26 // 28 // 2a // 2c // 2e END: // 30 JMP_OK: LDA DATA_1 SKZ HLT //SKZ or LDA did not work LDA DATA_2 SKZ JMP SKZ_OK HLT //SKZ or LDA did not work SKZ_OK: STO TEMP //store non-zero value in TEMP LDA DATA_1 STO TEMP //store zero value in TEMP LDA TEMP SKZ //check to see if STO worked HLT //STO did not work XOR DATA_2 SKZ //check to see if XOR worked JMP XOR_OK HLT //XOR did not work at all XOR_OK: XOR DATA_2 SKZ HLT //XOR did not switch all bits HLT //CONGRATULATIONS - TEST1 PASSED! JMP BEGIN //run test again 24
111_00000 // 3c TST_JMP: JMP JMP_OK 0000_0110
000_00000 // 3e HLT //JMP is broken //-----------------------------test1.pro的结束--------------------------------------------
/*********************************************************************************
下面文件中的数据在仿真时需要用系统任务$readmemb读入RAM,才能被上面的汇编程序test1.pro使用。
*************************************************************************************/ //------------------------------test1.dat开始------------------------------------------------ @00 //address statement at RAM
00000000 // 1800 DATA_1: //constant 00(hex) 11111111 // 1801 DATA_2: //constant FF(hex)
10101010 // 1802 TEMP: //variable - starts with AA(hex) //------------------------------test1.dat的结束-------------------------------------------
/****************************************************************************** * Test 2程序是用于验证RISC_ CPU的功能, 是设计工作的重要环节
* 本程序测试RISC_ CPU的高级指令集,如果RISC_ CPU的各条指令执行正确, * 它应在地址为20(hex)处,在执行HLT时停止运行。
* 如果该程序在任何其他地址暂停运行,则必有一条指令运行出错。 * 可参照注释找到出错的指令。
* 注意:必须 先在RISC_ CPU 上运行 test1程序成功后,才可运行本程序。
*****************************************************************************/ //-------------------------------test2.pro开始-------------------------------------------------------------------- //--- 机器码 地址 汇编助记符 注释 @00
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 //FF plus 1 makes -1 0000_0000
110_11000 // 16 STO TEMP 0000_0011
101_11000 // 18 LDA DATA_1 0000_0000
010_11000 // 1a ADD TEMP //-1 plus 1 should make zero 0000_0011
25
001_00000 // 1c SKZ 0000_0000
000_00000 // 1e HLT //ADD Doesn't work 0000_0000
000_00000 // 20 END: HLT //CONGRATULATIONS - TEST2 PASSED! 0000_0000
111_00000 // 22 JMP BEGIN //run test again 0000_0000
//----------------------------- test2.pro结束--------------------------------------------
/********************************************************************************** 下面文件中的数据在仿真时需要用系统任务$readmemb读入RAM,才能被上面的汇编程序test2.pro使用。 *************************************************************************************/ //------------------------------ test2.dat开始 ------------------------------------------------ @00
00000001 // 1800 DATA_1: //constant 1(hex) 10101010 // 1801 DATA_2: //constant AA(hex) 11111111 // 1802 DATA_3: //constant FF(hex) 00000000 // 1803 TEMP:
//------------------------------test2.dat结束 .--------------------------------------------------
/************************************************************************************ * Test 3 程序是一个计算从0到144的Fibonacci 序列的程序,用于进一步验证RISC_ CPU的功能。 * 所谓Fibonacci 序列就是一系列数其中每一个数都是它前面两个数的和(如:0,1,1,2,3,5, * 8,13,21,………..)。 这种序列常用于财务分析。
* 注意:必须在成功地运行前两个测试程序后才运行本程序 。否则很难发现问题所在。
************************************************************************************/
//-------------------------------test3.pro开始-------------------------------------------------------------------- //--- 机器码 地址 汇编助记符 注释 @00
101_11000 // 00 LOOP: LDA FN2 //load value in FN2 into accum 0000_0001
110_11000 // 02 STO TEMP //store accumulator in TEMP 0000_0010
010_11000 // 04 ADD FN1 //add value in FN1 to accumulator 0000_0000
110_11000 // 06 STO FN2 //store result in FN2 0000_0001
101_11000 // 08 LDA TEMP //load TEMP into the accumulator 0000_0010
110_11000 // 0a STO FN1 //store accumulator in FN1 0000_0000
100_11000 // 0c XOR LIMIT //compare accumulator to LIMIT 0000_0011
001_00000 // 0e SKZ //if accum = 0, skip to DONE 0000_0000
111_00000 // 10 JMP LOOP //jump to address of LOOP 0000_0000
000_00000 // 12 DONE: HLT //end of program 0000_0000
//----------------------------- test3.pro结束 --------------------------------------------
/***********************************************************************************
26
下面文件中的数据在仿真时需要用系统任务$readmemb读入RAM,才能被上面的汇编程序test3.pro使用。
*************************************************************************************/ //------------------------------test3.dat开始------------------------------------------------ @00
00000001 // 1800 FN1: //data storage for 1st Fib. No. 00000000 // 1801 FN2: //data storage for 2nd Fib. No. 00000000 // 1802 TEMP: //temproray data storage
10010000 // 1803 LIMIT: //max value to calculate 144(dec) //-----------------------------test3.dat结束--------------------------------------------
以下介绍前仿真的步骤,首先按照表示各模块之间连线的电路图编制测试文件,即定义Verilog的wire变量作为连线,连接各功能模块之间的引脚,并将输入信号引入,输出信号引出。如若需要,可加入必要的语句显示提示信息。例如,risc_cpu 的测试文件就是cputop.v。其次,使用仿真软件进行仿真,由于不同的软件使用方法可能有较大的差异,下面我们只简单地介绍modelsim 6.1的使用:
1) 建一个目录存放编写的设计代码文件,注意所有Verilog设计代码都必须用扩展名为.v的文本型
文件存档。(注意: 在计算机环境中将.v扩展名文件的打开方式设置为Modelsim); 2) 双点击cputop.v 便能自动地启动modelsim 6.1进入文件所在的目录
3) 点击modelsim 6.1台头菜单:FileNewLibrary 弹出一个配置框,给新的Library命名,例如
键入work,点击OK,在Transcript框内出现一些文字告诉操作者所在的目录和处理的文件和新建立的Library的名字,这次编译的很多信息将储存在这个由用户起名的Library中。
4) 点击modelsim 6.1台头菜单上类似多层文件的图标,弹出配置框,可设置Library,并可以把需
要编译的文件选中进行编译。 5) 编译成功后,用户将在workspace的框内发现用户命名的Library下面出现了编译通过的文件名,
如果设计仿真所需要的所有文件已经编译成功,就可以加载仿真代码。只需要双点击cputop即可,如果加载成功,Modesim自动地进入可以仿真的状态,只要配置好需要观察波形的信号,就可以点击台头菜单上的开始仿真的图标。至于如何配置需要观察的信号,不同的版本有些差别,试验几次就可以明白,这里就不再一一赘述。由于cputop.v编写了很好的输出显示,所以在在Transcript框内将显示以下信息,这些信息说明仿真工作正确无误。
仿真结果如下: run -all #
# ******************************************************
# * THE FOLLOWING DEBUG TASK ARE AVAILABLE: * # * \"test1; \" to load the 1st diagnostic progran. * # * \"test2; \" to load the 2nd diagnostic program. * # * \"test3; \" to load the Fibonacci program. *
# ****************************************************** #
# rom loaded successfully! # ram loaded successfully! #
# *** RUNNING CPUtest1 - The Basic CPU Diagnostic Program *** #
# TIME PC INSTR ADDR DATA # ---------- --- ----- ---- ----- # 1200.0 ns 0000 JMP 003c zz # 2000.0 ns 003c JMP 0006 zz # 2800.0 ns 0006 LDA 1800 00 # 3600.0 ns 0008 SKZ 0000 zz # 4400.0 ns 000c LDA 1801 ff # 5200.0 ns 000e SKZ 0000 zz
27
# 6000.0 ns 0010 JMP 0014 zz # 6800.0 ns 0014 STO 1802 ff # 7600.0 ns 0016 LDA 1800 00 # 8400.0 ns 0018 STO 1802 00 # 9200.0 ns 001a LDA 1802 00 # 10000.0 ns 001c SKZ 0000 zz # 10800.0 ns 0020 XOR 1801 ff # 11600.0 ns 0022 SKZ 0000 zz # 12400.0 ns 0024 JMP 0028 zz # 13200.0 ns 0028 XOR 1801 ff # 14000.0 ns 002a SKZ 0000 zz # 14800.0 ns 002e HLT 0000 zz #
# *********************************************************** # * A HALT INSTRUCTION WAS PROCESSED !!! *
# *********************************************************** #
# Break at H:/seda/w/Cputop.v line 109 run -continue
# rom loaded successfully! # ram loaded successfully! #
# *** RUNNING CPUtest2 - The Advanced CPU Diagnostic Program *** #
# TIME PC INSTR ADDR DATA # ---------- ---- ----- ---- ----- # 16200.0 ns 0000 LDA 1801 aa # 17000.0 ns 0002 AND 1802 ff # 17800.0 ns 0004 XOR 1801 aa # 18600.0 ns 0006 SKZ 0000 zz # 19400.0 ns 000a ADD 1800 01 # 20200.0 ns 000c SKZ 0000 zz # 21000.0 ns 000e JMP 0012 zz # 21800.0 ns 0012 XOR 1802 ff # 22600.0 ns 0014 ADD 1800 01 # 23400.0 ns 0016 STO 1803 ff # 24200.0 ns 0018 LDA 1800 01 # 25000.0 ns 001a ADD 1803 ff # 25800.0 ns 001c SKZ 0000 zz # 26600.0 ns 0020 HLT 0000 zz #
# *********************************************************** # * A HALT INSTRUCTION WAS PROCESSED !!! *
# *********************************************************** #
# Break at H:/seda/w/cputop.v line 111 run -continue
# rom loaded successfully! # ram loaded successfully! #
# *** RUNNING CPUtest3 - An Executable Program *** # *** This program should calculate the fibonacci *** #
# TIME FIBONACCI NUMBER # --------- ---------------------- # 33250.0 ns 0 # 40450.0 ns 1 # 47650.0 ns 1 # 54850.0 ns 2
28
# 62050.0 ns 3 # 69250.0 ns 5 # 76450.0 ns 8 # 83650.0 ns 13 # 90850.0 ns 21 # 98050.0 ns 34 # 105250.0 ns 55 # 112450.0 ns 89 # 119650.0 ns 144 #
# *********************************************************** # * A HALT INSTRUCTION WAS PROCESSED !!! *
# *********************************************************** #
在运行了以上程序后,如仿真程序运行的结果正确,RTL仿真(即布局布线前的仿真)可告结束。
17.6.2. RISC_CPU模块的综合
在对所设计的RISC_CPU模型进行验证后,如没有发现问题就可开始做下一步的工作即综合。综合工作往往要分阶段来进行,这样便于发现问题。
所谓分阶段就是指:
第一阶段:先对构成RISC_CPU模型的各个子模块,如状态控制机模块(包括machine模块,machinectl模块)、指令寄存器模块(register模块)、算术逻辑运算单元模块(alu模块)等,分别加以综合以检查其可综合性,综合后及时进行后仿真,这样便于及时发现错误,及时改进。
DATA[7:0]\\I DATA[7:0] 1P CLK 12P RD\\I RD 5P RST 11P WR\\I CPU 6P WR HALT\\I HALT 7P ADDR[12:0]\\I ADDR[12:0] 8P 图14 用于综合的RISC_CPU模块(CPU.v)
第二阶段:把要综合的模块从仿真测试信号模块和虚拟外围电路模型(如ROM模块、RAM模块、显示部件模块等)中分离出来,组成一个独立的模块,其中包括了所有需要综合的模块。然后给这个大模块起一个名字,如本章中的例子,我们要综合的只是RISC_CPU并不包括虚拟外围电路,可以给这一模块起一个名字,例如称它为CPU.v模块,见前面测试程序解释时介绍的CPU.v模块。如用电路图描述的话,我们还需给它的引脚加上标准的引脚部件并加标记,见图14。
29
第三阶段:把需要综合的模块加载到综合器,本例所使用的综合器是独立的Synplify pro 8.1, 选定的 FPGA 是 Altera Stratixii,针对它的库进行综合,我们也可以使用Quartus II 或其他综合工具进行综合。综合的结果会产生一系列的文件,其中有一个文件报告了综合的过程、设计中所使用的基本单元、各部件的时间参数等。下面的报告就是综合cpu.v时生成的综合报告片段,综合所用的库为Altera Stratix ii 系列的FPGA库,约定的时钟频率为80MHz。
//-------------------- RISC_CPU芯片综合结果报告开始---------------------------
#Program: Synplify Pro 8.1 #OS: Windows_NT
$ Start of Compile
#Wed May 23 17:02:14 2007
Synplicity Verilog Compiler, version 3.1.0, Build 049R, built May 3 2005 Copyright (C) 1994-2005, Synplicity Inc. All Rights Reserved
@I::\"C:\\Program Files\\Synplicity\\fpga_81\\lib\\altera\\altera.v\" @I::\"C:\\Program Files\\Synplicity\\fpga_81\\lib\\altera\\altera_mf.v\" @I::\"C:\\Program Files\\Synplicity\\fpga_81\\lib\\altera\\altera_lpm.v\" @I::\"C:\\vlogexe\\ex17_2\\cpu.v\"
@I:\"C:\\vlogexe\\ex17_2\\cpu.v\":\"C:\\vlogexe\\ex17_2\\clk_gen.v\" @I:\"C:\\vlogexe\\ex17_2\\cpu.v\":\"C:\\vlogexe\\ex17_2\\accum.v\" …………………….. ……………………..
Verilog syntax check successful!
Compiler output is up to date. No re-compile necessary
Selecting top level module cpu
@N:\"C:\\vlogexe\\ex17_2\\clk_gen.v\":2:7:2:13|Synthesizing module clk_gen
@N: CL201 :\"C:\\vlogexe\\ex17_2\\clk_gen.v\":18:0:18:5|Trying to extract state machine for register state Extracted state machine for register state
State machine has 9 reachable states with original encodings of: 00000000 00000001 00000010 …………. ………….
@N:\"C:\\vlogexe\\ex17_2\\register.v\":4:7:4:14|Synthesizing module register ………….. …………..
@N: CL201 :\"C:\\vlogexe\\ex17_2\\machine.v\":44:0:44:5|Trying to extract state machine for register state Extracted state machine for register state
State machine has 8 reachable states with original encodings of: 000 001 010 …..
@N:\"C:\\vlogexe\\ex17_2\\datactl.v\":11:7:11:13|Synthesizing module datactl
@N:\"C:\\vlogexe\\ex17_2\\adr.v\":10:8:10:10|Synthesizing module adr …………... ………….. @END
Process took 0h:00m:01s realtime, 0h:00m:01s cputime # Wed May 23 17:02:14 2007
###########################################################[ Version 8.1
Synplicity Altera Technology Mapper, Version 8.1.0, Build 539R, Built May 6 2005 Copyright (C) 1994-2005, Synplicity Inc. All Rights Reserved
Automatic dissolve at startup in view:work.cpu(verilog) of m_counter(counter) Automatic dissolve at startup in view:work.cpu(verilog) of m_adr(adr) ……………. ……………..
RTL optimization done.
@N:\"c:\\vlogexe\\ex17_2\\counter.v\":19:0:19:5|Found counter in view:work.cpu(verilog) inst m_counter.pc_addr[12:0] Encoding state machine work.clk_gen(verilog)-state[8:0] original code -> new code 00000000 -> 000000000 00000001 -> 000000011 …………….
Encoding state machine work.machine_synplcty(verilog)-state[7:0] original code -> new code 000 -> 00000000 001 -> 00000011
30
………………
Writing Analyst data base C:\\vlogexe\\ex17_2\\rev_1\\cpu.srm Writing Verilog Netlist and constraint files Writing .vqm output for Quartus
Writing Cross reference file for Quartus to C:\\vlogexe\\ex17_2\\rev_1\\cpu.xrf Writing Verilog Simulation files
Found clock cpu|clk with period 12.50ns
Found clock machine|inc_pc_derived_clock with period 12.50ns
##### START OF TIMING REPORT #####[
# Timing Report written on Wed May 23 17:02:16 2007
Top view: cpu Requested Frequency: 80.0 MHz Wire load mode: top Paths requested: 5 Constraint File(s):
@N: MT195 |This timing report estimates place and route data. Please look at the place and route timing report for final timing..
@N: MT197 |Clock constraints cover only FF-to-FF paths associated with the clock.. Performance Summary *******************
Worst slack in design: 10.158
Requested Estimated Requested Estimate Clock Clock
Starting Clock Frequency Frequency Period Period Slack Type Group -----------------------------------------------------------------------------------------------------------------------------------------------------------------
cpu|clk 80.0 MHz 427.0 MHz 12.500 2.342 10.158 inferred Inferred_ clkgroup_0 machine|inc_pc_derived_clock 80.0 MHz 427.0 MHz 12.500 2.342 10.661 derived Inferred_clkgroup_0 =====================================================================================================
Clock Relationships ******************* ……………… ………………
==================================== Detailed Report for Clock: cpu|clk
==================================== Starting Points with Worst Slack
********************************
Starting Arrival Instance Reference Type Pin Net Time Slack
Clock --------------------------------------------------------------------------------------------------------------------------------------------------- m_accum.accum[0] cpu|clk stratixii_lcell_ff regout accum_0 0.095 10.158 m_accum.accum[1] cpu|clk stratixii_lcell_ff regout accum_1 0.095 10.194 .................... .....................
======================================================================================================
Ending Points with Worst Slack
****************************** ……………. ……………
Worst Path Information
***********************
Path information for path number 1:
Requested Period: 12.500 - Setup time: 0.403 = Required time: 12.097
- Propagation time: 1.939 = Slack (critical) : 10.158
Number of logic level(s): 9
Starting point: m_accum.accum[0] / regout Ending point: m_alu.alu_out[7] / adatasdata The start point is clocked by cpu|clk [rising] on pin clk The end point is clocked by cpu|clk [rising] on pin clk
Instance / Net Pin Pin Arrival No. of Name Type Name Dir Delay Time Fan Out(s) ----------------------------------------------------------------------------------------------------------------------------------------------------------- m_accum.accum[0] stratixii_lcell_ff regout Out 0.095 0.095 - accum_0 Net - - 0.621 - 4 m_alu.un2_alu_out_carry_0 stratixii_lcell_comb dataf In - 0.716 -
31
m_alu.un2_alu_out_carry_0 stratixii_lcell_comb cout Out 0.312 1.028 - un2_alu_out_carry_0 Net - - 0.000 - 1 m_alu.un2_alu_out_carry_1 stratixii_lcell_comb cin In - 1.028 - ………………….. …………………..
=================================================================================================== Total path delay (propagation time + setup) of 2.342 is 1.307(55.8%) logic and 1.035(44.2%) route.
====================================
Detailed Report for Clock: machine_synplcty|inc_pc_derived_clock ====================================
Starting Points with Worst Slack
********************************
Starting Instance Reference Type Pin Net Arrival Time Slack
Clock --------------------------------------------------------------------------------------------------------------------------------------------------- m_counter.pc_addr[0] inc_pc_clock stratixii_lcell_ff regout pc_addr_c_0 0.095 10.661 m_counter.pc_addr[1] inc_pc_clock stratixii_lcell_ff regout pc_addr_c_1 0.095 10.697 .................... .....................
============================================================================================
Ending Points with Worst Slack *************************** ........................ .......................
Path information for path number 1:
Requested Period: 12.500 - Setup time: 0.247 = Required time: 12.253
- Propagation time: 1.592 = Slack (non-critical) : 10.661
Number of logic level(s): 13
Starting point: m_counter.pc_addr[0] / regout Ending point: m_counter.pc_addr[12] / datain
The start point is clocked by machine_synplcty|inc_pc_derived_clock [rising] on pin clk The end point is clocked by machine_synplcty|inc_pc_derived_clock [rising] on pin clk
Instance / Net Pin Pin Arrival No. of Name Type Name Dir Delay Time Fan Out(s) ------------------------------------------------------------------------------------------------------------------------------------------- m_counter.pc_addr[0] stratixii_lcell_ff regout Out 0.095 0.095 -
pc_addr_c_0 Net - - 0.621 - 3 m_counter.pc_addr_c0 stratixii_lcell_comb datad In - 0.716 -
m_counter.pc_addr_c0 stratixii_lcell_comb cout Out 0.354 1.070 -
pc_addr_c0_cout Net - - 0.000 - 1 ....................... .......................
====================================================================================================== Total path delay (propagation time + setup) of 1.839 is 1.218(66.2%) logic and 0.621(33.8%) route.
##### END OF TIMING REPORT #####]
##### START OF AREA REPORT #####[ Design view:work.cpu(verilog) Selecting part EP2S15F484C3
@N: FA174 |The following device usage report estimates place and route data. Please look at the place and route report for final resource usage..
Total combinational functions 76 ALUT usage by number of inputs 7 input functions 0 6 input functions 8 5 input functions 7 4 input functions 6 <=3 input functions 55 ALUTs by mode normal mode 55 extended LUT mode 0
32
arithmetic mode 21 shared arithmetic mode 0 Total registers 74 I/O pins 56
DSP Blocks: 0 (0 nine-bit DSP elements).
DSP Utilization: 0.00% of available 12 blocks (96 nine-bit). ShiftTap: 0 (0 registers) MRAM: 0 (0% of 0) M4Ks: 0 (0% of 78) M512s: 0 (0% of 104) Total ESB: 0 bits
##### END OF AREA REPORT #####]
Mapper successful!
Process took 0h:0m:1s realtime, 0h:0m:1s cputime
###########################################################]
//-------------------- RISC_CPU芯片综合结果报告结束---------------------------
在以上的报告文件中,揭示了综合器对综合过程和结果的分析,有极其重要的意义,它能帮助设计者了解系统运行的最高时钟、关键路径的最大延迟,使用的逻辑部件的种类和数目。当出现问题时会提示我们Verilog源代码的哪个模块第几行有错误或警告。这些资料的熟练应用和分析是很重要的。它能提高设计的工作效率,使综合器综合出更加合理的电路。关于如何利用综合器能处理的综合指令和属性,作者将在高级篇中介绍。综合指令和属性是Verilog源代码中的一种符合特殊规定的注释行,仿真工具不处理这样的注释行,而综合器却能识别这些符合特殊要求的注释行,根据设计者通过综合指令提出的要求,使综合器综合出更好的符合设计者要求的电路结构。
17.6.3.RISC_CPU模块的优化和布局布线
选定元件库后就可以对所设计的RISC_CPU模型进行综合,综合工作是把Verilog RTL代码通过综合工具,产生了一系列由现存元件的逻辑网表组成的文件。在综合工具上通过选择项可以配置生成逻辑网表文件的格式。逻辑网表文件可以是:Verilog Netlist、 VHDL Netlist或者电子设计交换格式(Electronic Design Interchange Format)这也就是在电路设计工业界常说的EDIF格式文件。在产生了这些文件之后,就可以进行综合后的网表仿真。网表仿真的Verilog模型只是对应库逻辑元件的行为模型,并不涉及器件和布局布线的连接线延迟,因此与实际电路的行为还存在着差异,这种仿真模型没有明显的延迟。为了知道实现电路真实的带延迟的行为,还必须进行布局布线操作,以便生成实际电路和连接线带延迟的行为模型。
下面我们将介绍如何用Altera QuartusII进行综合和布局布线, 由RTL代码产生由对应元件库(Altera StritixII)Verilog网表组成的仿真模型以及该网表所提取的延迟参数文件。用QuartusII进行综合和布线布线的步骤如下:
1)双点击QuartusII图标,启动QuartusII工具。
2)在QuartusII主窗口的台头工具栏中选择File New Project Wizard…. ,随即弹出对话框,
在相应的空格栏选取或者填入工作目录名、项目名和被综合模块组的顶层模块名;
3)点击Next,随即弹出另外一个对话框,在相应的空格栏中选取或者填入希望被综合的文件
名,点击ADD,添加该文件进入综合环境; 4)点击Next,随即又弹出一个对话框,在相应的空格栏中选取或者填入实现逻辑的器件型号。 5)点击Next,随即又弹出一个对话框,选取EDA仿真工具,在出现的空格框内选取ModelSim
和Verilog格式;
6)点击Next,仔细阅读设计者已经配置环境的情况,然后再点击finish,结束综合环境的的
配置过程。
7)在QuartusII主窗口的台头工具栏中选择Processing start compilation,或者直接点击三角
型的图标随即开始编译过程。
8)在工作目录中会出现一个新的名为simulation的目录打开这个目录,可以看到一个名为
Modelsim的目录,再打开这个目录,可以看到三个文件,分别为xxx.vo,xxx_modelsim.xrf,和xxx_v.sdo。
9)将xxx.vo和xxx_v.sdo拷贝到工作目录,将xxx.vo文件替换原来的xxx.v 文件再进行一次
33
仿真就能将xxx_v.sdo的延迟信息带入,得到布局布线后的仿真结果。
10)需要注意的是布局布线后的仿真还必须有已经选择库的仿真模型才能进行,这些库究竟
在哪里呢?我们可以在Altera QuartusII的安装目录里寻找。如果Altera QuartusII安装在硬盘C上,则在C:\\altera\\61\\quartus\\eda\\sim_lib目录下可以看到许多型号FPGA的元件库的仿真模型,如果用的是Verilog模型,我们只需要在扩展名为.v的文件中寻找即可,把相应型号的FPGA库元件的仿真模型拷贝到自己的工作目录进行编译就能进行布局布线后的仿真了。
综合和布局布线完成后得到两个文件cpu.vo,cpu_v.sdo和 cpu_modelsim.xrf。cpu.vo是所设计的RISC_CPU的门级结构,即利用Verilog 语法描述的用stratixii 型号FPGA库中的基本逻辑电路元件构成的复杂电路连线网络,而cpu_v.sdo是布局布线的延迟参数文件,stratixii_atoms.v 是cpu.vo所引用的Verilog门级模型的库文件,包含了各种基本逻辑电路的门级模型,它们的参数与真实器件完全一致,包括如延迟等参数。需要注意的是:必须在布线工具的相关界面上选取生成输出文件的格式为Verilog后,cpu.vo和cpu_v.sdo这两个文件才会产生。 将这两个文件和stratixii_atoms.v包含在cputop.v中,来代替原来的RTL模块,cpu.v。其他外围测试行为模块相同,用仿真器再进行一次仿真,此时称为布局布线后仿真。实际上,后仿真与前仿真的根本区别在于测试文件所包含模型的结构不同。前仿真使用的是RTL级模型,如cpu.v,而后仿真使用的是真实的门级结构模型,其中不但有逻辑关系,还包含实际门级电路和布线的延迟,还有驱动能力的问题。仔细观察后仿真波形就会发现与前仿真有一些不同,各信号的变化与时钟沿之间存在着延迟,这些仿真信息在前仿真时并未反映出来。
图 15 后仿真波形
下面的Verilog程序是由布局布线工具生成的,分别命名为cpu.vo和cpu_v.sdo 。由于cpu.vo是门级描述,共有上千行,而cpu_v.sdo 是延迟参数文件,也有几百行。而stratixii_atoms.v是库元件,包含的逻辑元件非常多,也无法在课本上列出其全部程序,只能从中截取一小片段供同学参考。有兴趣的同学可以查看生成的代码并参考 Verilog 语法手册中有关门级描述和用户自定义源语(UDP)来理解这些代码。由于这些代码是Verilog的门级模型,又有布线的延迟,所以可以来验证电路结构是否符合设计要求。下面列出了这三个可用于布局布线后仿真的,用来代替RTL 描述的cpu.v的Verilog文件片段供同学参考,帮助同学理解布线后门级仿真的原理。
/******************** cpu.vo开始 ************************ // Copyright (C) 1991-2006 Altera Corporation
// Your use of Altera Corporation's design tools, logic functions …………… ……………
// VENDOR \"Altera\"
// PROGRAM \"Quartus II\"
// VERSION \"Version 6.1 Build 201 11/27/2006 SJ Full Version\"
// DATE \"05/20/2007 14:15:10\"
// Device: Altera EP2S15F484C3 Package FBGA484
34
// This Verilog file should be used for ModelSim (Verilog) only
`timescale 1 ps/ 1 ps
module cpu ( clk, reset, halt, rd, wr, addr, data, opcode, fetch, ir_addr, pc_addr); input clk; input reset; output halt; output rd; output wr; output [12:0] addr; inout [7:0] data; output [2:0] opcode; output fetch; output [12:0] ir_addr; output [12:0] pc_addr;
wire gnd = 1'b0; wire vcc = 1'b1;
tri1 devclrn; tri1 devpor; tri1 devoe;
// synopsys translate_off
initial $sdf_annotate(\"cpu_v.sdo\"); // synopsys translate_on
wire \\m_machine|inc_pc ;
wire \\m_machine|always0~73 ; wire \\m_machine|always0~74 ; ……… ………
// atom is at LCFF_X21_Y13_N27
stratixii_lcell_ff \\m_machine|inc_pc~I ( .clk(\\clk~clkctrl ), .datain(\\m_machine|Selector0~168 ), .adatasdata(gnd), .aclr(gnd), .aload(gnd), .sclr(!\\m_machinecl|ena ), .sload(gnd), .ena(vcc), .devclrn(devclrn), .devpor(devpor), .regout(\\m_machine|inc_pc ));
……………… ………………
// atom is at PIN_C11
stratixii_io \\pc_addr[12]~I ( .datain(\\m_counter|pc_addr [12]), .ddiodatain(gnd), .oe(vcc), .outclk(gnd), .outclkena(vcc), .inclk(gnd), .inclkena(vcc), .areset(gnd), ……….
…………);
// synopsys translate_off
defparam \\pc_addr[12]~I .ddio_mode = \"none\";
defparam \\pc_addr[12]~I .ddioinclk_input = \"negated_inclk\"; ……………. …………….
// synopsys translate_on
endmodule
/*********************cpu.vo结束*******************
35
/******************** cpu_v.sdo 开始********************* // Copyright (C) 1991-2006 Altera Corporation
// Your use of Altera Corporation's design tools, logic functions …………….. ……………..
// Device: Altera EP2S15F484C3 Package FBGA484
// This SDF file should be used for ModelSim (Verilog) only
(DELAYFILE
(SDFVERSION \"2.1\") (DESIGN \"cpu\")
(DATE \"05/20/2007 14:15:10\") (VENDOR \"Altera\")
(PROGRAM \"Quartus II\")
(VERSION \"Version 6.1 Build 201 11/27/2006 SJ Full Version\") (DIVIDER .)
(TIMESCALE 1 ps)
(CELL
(CELLTYPE \"stratixii_lcell_ff\") (INSTANCE m_machine\\|inc_pc\\~I) (DELAY
(ABSOLUTE
(PORT clk (1240:1240:1240) (1285:1285:1285)) (PORT datain (155:155:155) (155:155:155)) (PORT sclr (1174:1174:1174) (1124:1124:1124))
(IOPATH (posedge clk) regout (94:94:94) (94:94:94)) ) )
(TIMINGCHECK
(SETUP datain (posedge clk) (90:90:90)) (SETUP sclr (posedge clk) (90:90:90))
(HOLD datain (posedge clk) (149:149:149)) (HOLD sclr (posedge clk) (149:149:149)) ) )
(CELL
(CELLTYPE \"stratixii_lcell_comb\")
(INSTANCE m_machine\\|always0\\~73_I) (DELAY
(ABSOLUTE
(PORT dataa (279:279:279) (282:282:282)) …………….
(IOPATH dataa combout (366:366:366) (366:366:366)) ……………. ) ) )
………….. ……………… ……………. (CELL
(CELLTYPE \"stratixii_asynch_io\") (INSTANCE pc_addr\\[12\\]\\~I.inst1) (DELAY
(ABSOLUTE
(PORT datain (1606:1606:1606) (1956:1956:1956))
(IOPATH datain padio (1998:1998:1998) (1998:1998:1998)) ) ) ) )
//---------------------------- cpu_v.sdo的结束 -----------------------
//----------------------------stratixii_atom.v 的开始 ----------------------- // Copyright (C) 1991-2006 Altera Corporation ……………… ………………
// ********** PRIMITIVE DEFINITIONS **********
`timescale 1 ps/1 ps
// ***** DFFE
primitive STRATIXII_PRIM_DFFE (Q, ENA, D, CLK, CLRN, PRN, notifier); input D; input CLRN;
36
input PRN; input CLK; input ENA; input notifier; output Q; reg Q;
initial Q = 1'b0;
table
// ENA D CLK CLRN PRN notifier : Qt : Qt+1
(??) ? ? 1 1 ? : ? : -; // pessimism x ? ? 1 1 ? : ? : -; // pessimism 1 1 (01) 1 1 ? : ? : 1; // clocked data ………………………… ………………………… endtable
endprimitive
module stratixii_dffe ( Q, CLK, ENA, D, CLRN, PRN ); input D; input CLK; input CLRN; input PRN; input ENA; output Q;
wire D_ipd; wire ENA_ipd; wire CLK_ipd; wire PRN_ipd; wire CLRN_ipd;
buf (D_ipd, D);
buf (ENA_ipd, ENA); buf (CLK_ipd, CLK); buf (PRN_ipd, PRN); buf (CLRN_ipd, CLRN);
wire legal; reg viol_notifier;
STRATIXII_PRIM_DFFE ( Q, ENA_ipd, D_ipd, CLK_ipd, CLRN_ipd, PRN_ipd, viol_notifier ); and(legal, ENA_ipd, CLRN_ipd, PRN_ipd);
specify
specparam TREG = 0; specparam TREN = 0; specparam TRSU = 0; specparam TRH = 0; specparam TRPR = 0; specparam TRCL = 0;
$setup ( D, posedge CLK &&& legal, TRSU, viol_notifier ) ; $hold ( posedge CLK &&& legal, D, TRH, viol_notifier ) ; $setup ( ENA, posedge CLK &&& legal, TREN, viol_notifier ) ; $hold ( posedge CLK &&& legal, ENA, 0, viol_notifier ) ;
( negedge CLRN => (Q +: 1'b0)) = ( TRCL, TRCL) ; ( negedge PRN => (Q +: 1'b1)) = ( TRPR, TRPR) ; ( posedge CLK => (Q +: D)) = ( TREG, TREG) ;
endspecify endmodule
………………..
//------------------------------------------------------------------
`timescale 1 ps/1 ps
module stratixii_termination ( rup, rdn,
terminationclock, terminationclear,
……. …….);
37
input rup; input rdn; input terminationclock; input terminationclear; ……………… ………………;
parameter runtime_control = \"false\"; parameter use_core_control = \"false\"; ……………… ………………
// BUFFERED BUS INPUTS wire rup_in; wire rdn_in; wire clock_in; ……….. ………..
// TMP OUTPUTS
wire incrup_out; wire incrdn_out; wire [13:0] control_out; …………… ……………
// FUNCTIONS
// INTERNAL NETS AND VARIABLES
// TIMING HOOKS buf (rup_in, rup); buf (rdn_in, rdn);
buf (clock_in,terminationclock); ……….
specify
(posedge terminationclock => (terminationcontrol +: control_out)) = (0,0);
(posedge terminationclock => (terminationcontrolprobe +: controlprobe_out)) = (0,0); endspecify
// output driver
buf buf_ctrl_out [13:0] (terminationcontrol,control_out);
buf buf_ctrlprobe_out [6:0] (terminationcontrolprobe,controlprobe_out);
// MODEL
assign incrup = incrup_out; assign incrdn = incrdn_out;
assign incrup_out = (power_down == \"true\") ? (enable_in & rup_in) : rup_in; …………… ……………
stratixii_termination_digital rup_block( .rin(incrup_out), .clk(clock_in), .clr(clear_in), .ena(ena1),
.padder(pulldown_in), .devpor(devpor), .devclrn(devclrn),
.ctrlout(rup_control_out) );
defparam rup_block.runtime_control = runtime_control; defparam rup_block.use_core_control = use_core_control; …………… ……………
stratixii_termination_digital rdn_block( .rin(incrdn_out), .clk(clock_in), .clr(clear_in), .ena(ena1),
.padder(pullup_in), .devpor(devpor), .devclrn(devclrn),
.ctrlout(rdn_control_out) );
defparam rdn_block.runtime_control = runtime_control;
38
defparam rdn_block.use_core_control = use_core_control; …………
endmodule
//------------------------------------------------------------------ // Module Name : stratixii_routing_wire
// Description : Simulation model for a simple routing wire //------------------------------------------------------------------
`timescale 1ps / 1ps
module stratixii_routing_wire (datain, dataout ); // INPUT PORTS input datain;
// OUTPUT PORTS output dataout;
// INTERNAL VARIABLES wire dataout_tmp;
specify
(datain => dataout) = (0, 0) ;
endspecify
assign dataout_tmp = datain;
and (dataout, dataout_tmp, 1'b1);
endmodule // stratixii_routing_wire
//----------------------------stratixii_atom.v 的结束 -----------------------
从上面提供的带门延迟的布局布线门级网表,我们可以了解布局布线后产生的带 .vo 扩展名的网表的实质。从本质上说这是一种比综合器产生的更接近实际电路结构的Verilog门级和源语基础部件级源代码。这种代码有自己的仿真行为,但也有确定的电路制造参数与之对应,所以是可以实现的,上面列出的代码明确地说明了这个问题。对于源语基础部件级Verilog语法的深入了解是微电子工艺师和电路系统设计师都必须了解和掌握的,但苦于时间和篇幅的问题,我们将在高级篇里作深入的讲解。
不同FPGA厂家的布局布线工具提供不同的后仿真解决方法。所以很难用一句话作全面的介绍,读者应阅读FPGA厂家的布局布线工具的说明书中有关章节,选用正确的Verilog门级结构的后仿真解决方案。如后仿真正确无误,就可以把布局布线后生成的一系列文件送ASIC厂家或加载到FPGA器件的编码工具,使其变为专用的电路芯片。如后仿真中发现有错误,可先降低测试信号模块的主时钟频率,如该问题解决了,则需要找到造成问题的关键路径,下一次在布局布线时应先布关键的路径(即在约束文件中注明该路径是关键路径后,再重做自动布局布线),若还有问题则需检查各模块中是否有个别模块没有按照同步设计的原则。若是,则需改写有关的VerilogHDL模块。重复以上工作,直到后仿真正确无误。以上所述的就是用Verilog设计复杂数字电路系统的步骤。读者可以参考以上步骤,自己来设计一个可在FPGA上实现的小RISC_CPU系统。
总结:
从上面的例子我们可以看到,复杂的RISC_CPU设计其实是一个从抽象到具体的逐步接近的分析和实现过程。一个大型的设计先从概念出发,用Verilog写出抽象的功能块描述,把许多复杂的细节掩盖起来。 然后,从行为级分析功能块之间的关系。通过仿真逐步验证,发现问题,改动模块代码使其逐步趋向合理,并最后可以用RTL级Verilog 源代码模块来表示。接下去就可以通过自动综合工具把RTL级Verilog 源代码模块综合成电路网表。再通过布局布线工具让它们更具体化。在基础器件源语级基础上的系统精确仿真结果正确,可以使系统电路芯片的制作有 95% 以上的一次流片成功把握。这就是我们为什么要学习Verilog高层次先进设计方法的原因。
思考题:
1) 请叙述一下设计一个复杂数字系统的步骤。
39
2) 综合一个大型的数字系统需要注意什么?
3) 请改进以上RISC_CPU,把指令数增至16,寻址空间降为4K;书写设计报告,实现三个层次的仿真运行。
4) 什么叫软硬件联合仿真? 为什么说Verilog语言支持软硬件联合设计?
40
因篇幅问题不能全部显示,请点此查看更多更全内容