一、前言

  从研究生开始到工作半年,陆续在接触MCU SOC这些以CPU为核心的控制器,但由于专业的原因一直对CPU的内部结构和工作原理一知半解。今天从一篇博客中打破一直以来的盲区。特此声明,本文设计思想及代码均源于如下博文,这里仅用于自己学习记录,以及分享心得之用。

简易CPU的设计和实现_阡飞陌-CSDN博客
https://blog.csdn.net/weixin_36077867/article/details/82286612

二、简易CPU结构与工作原理概述

用下原文中的结构图:

  CPU核心模块包括控制器、程序计数器(PC)、存储器(memory)、译码器和算术逻辑单元(ALU)。控制器负责指挥调度各个模块正常工作:PC每到达一个数阶段内,均会进行取指令->译码->执行指令。取指令从memory中取出PC值指向地址的数据,之后数据传入译码器翻译为具体操作目的,最后根据这一目标来让ALU完成算数和逻辑运算,并将运算结果保存到memory指定地址。memory的内容就是在我们之前玩单片机时用IDE将C/C++等高级语言转化成的比特流,里边包括了代码指令、临时变量及所有需要保存的数据数值。

三、设计代码与仿真分析

  以下代码仅是对转载博客中进行了少许改动,并无实质变化。

 `timescale 1ns / 1ps

 // Description:
// program counter module PC
#(parameter ADDR_WIDTH = )
(
input clock,
input reset,
input en,
output reg [ADDR_WIDTH-:] pc
); wire [ADDR_WIDTH-:] pc_next; always@(posedge clock or posedge reset)begin
if(reset)
pc <= ;
else if(en)
pc <= pc_next;
end assign pc_next = pc + ; endmodule

PC.v

 `timescale 1ns / 1ps

 // Description:
// memory used for storing instructions, temporary variables, and initialization data
//STA,store A to
//LDA, load A from module memory
#(
parameter ADDR_WIDTH = ,
parameter DATA_WIDTH =
)
(
input clock,
input reset,
input wr_en,
input rd_en,
input [ADDR_WIDTH-:] addr,
input [DATA_WIDTH-:] din,
output reg [DATA_WIDTH-:] dout
); reg [DATA_WIDTH-:] mem [:-]; always@(posedge clock,posedge reset)begin
if(reset)begin
mem [] <= 'b000_01011; //LDA 01011
mem [] <= 'b010_01100; //ADD 01100
mem [] <= 'b001_01101; //STA 01101
mem [] <= 'b000_01011; //LDA 01011
mem [] <= 'b100_01100; //AND 01100
mem [] <= 'b001_01110; //STA 01110
mem [] <= 'b000_01011; //LDA 01011
mem [] <= 'b011_01100; //SUB 01100
mem [] <= 'b001_01111; //STA 01111
mem [] <= 'b10100000; //HLT
mem [] <= 'b00000000;
mem [] <= 'b10010101;
mem [] <= 'b01100101;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
mem [] <= 'b00000000;
end
else begin
if(wr_en)
mem[addr] <= din;
else if(rd_en)
dout <= mem[addr];
end
end
endmodule

memory.v

`timescale 1ns / 1ps

// Description:
// instruction decoder module idec
#(
parameter DATA_WIDTH = ,
parameter ADDR_WIDTH =
)
(
input clock,
input reset,
input en,
input [DATA_WIDTH-:] instruction,//from memory
output reg [DATA_WIDTH-ADDR_WIDTH-:] opcode,
output reg [ADDR_WIDTH-:] addr
); always@(posedge clock,posedge reset)begin
if(reset)begin
opcode <= ;
addr <= ;
end
else if(en)begin
opcode <= instruction[DATA_WIDTH- -:];
addr <= instruction[ADDR_WIDTH-:];
end
end endmodule

idec.v

 `timescale 1ns / 1ps

 // Description:
// arithmetic logic unit module alu
#(parameter OP_WIDTH = )
(
input clock,
input reset, input en,
input add_en,//加法运算使能
input sub_en,
input and_en,
input pass_en,
input [OP_WIDTH-:] din, output n,//负标志
output z,//0标志
output reg c,//输出进位标志
output v,//输出溢出标志
output reg [OP_WIDTH-:] a//累加器输出寄存器 dout ); assign n = (c == ) ? : ; //负数标志,如果进位标志为1,则n=1
assign z = (a == 'd0) ? 1: 0 ; //0标志,如果累加器为0,z=1
assign v = ((a>**(OP_WIDTH-)-) || (a<-**(OP_WIDTH-)) ? : ); //溢出标志 补码取值范围:-2^(n-1)~~~~~2^(n-1)-1 n=8 always @(posedge clock or posedge reset)begin
if (reset) begin
a <= ; //复位累加器清0,
c <= ;
end
else begin
if(en) begin
if(add_en)
{c,a} <= a + din;
else if(sub_en)
{c,a} <= a - din;
else if(and_en)
a <= a & din;
else if(pass_en)
a <= din;
end
end
end endmodule

alu.v

 `timescale 1ns / 1ps

 module control#(
parameter DATA_WIDTH = ,
parameter ADDR_WIDTH =
)
(
input clock,
input reset,
input [DATA_WIDTH-ADDR_WIDTH-:] opcode,//来自解码器解码后指令 output reg [-:] s,//使能信号
output reg addr_sel,//程序或数据地址选通
output reg [-:] instrs ); parameter [DATA_WIDTH-ADDR_WIDTH-:] LDA = 'b000,
STA = 'b001,
ADD = 'b010,
SUB = 'b011,
AND = 'b100; reg [-:] cnt;
wire add_cnt,end_cnt; always@(posedge clock, posedge reset)begin
if(reset)
cnt <= ;
else if(add_cnt)begin
if(end_cnt)
cnt <= ;
else
cnt <= cnt + ;
end
end assign add_cnt = ;
assign end_cnt = add_cnt && cnt == -; always@(*)begin
case(cnt)
:begin//取指令
s = 'b100_000;
addr_sel = ;
instrs = ;
end
:begin//解码
s = 'b010_000;
addr_sel = ;
end
:begin//read from the memory
addr_sel = ;
if(
(opcode == LDA) ||
(opcode == ADD) ||
(opcode == SUB) ||
(opcode == AND)
)
s = 'b001_000;
else
s = 'b000_000;
end
:begin//ALU operations
s = 'b000_100;
addr_sel = ;
case(opcode)
LDA:instrs = 'b0001;
ADD:instrs = 'b1000;
SUB:instrs = 'b0100;
AND:instrs = 'b0010;
STA:instrs = 'b0000;
default:instrs = 'b0000;
endcase
end
:begin//write to the memory
addr_sel = ;
if(opcode == STA)
s = 'b000_010;
else
s = 'b000_000;
end
:begin// PC
s = 'b000_001;
addr_sel = ;
end
default:begin
s = 'b000_000;
addr_sel = ;
instrs = ;
end
endcase
end endmodule

control.v

 `timescale 1ns / 1ps

 module cpu_top
(
input clock,
input reset, output n,//负标志
output z,//0标志
output c,//输出进位标志
output v//输出溢出标志
); parameter DATA_WIDTH = ,
ADDR_WIDTH = ; wire [-:] s;
wire [ADDR_WIDTH-:] addr_mem,addr_idec,addr_pc;
wire addr_sel;
wire [DATA_WIDTH-:] dout_mem,din_mem;
wire [DATA_WIDTH-ADDR_WIDTH-:] opcode;
wire [-:] alu_oper; assign addr_mem = addr_sel == ? addr_idec: addr_pc; control#(
.DATA_WIDTH (DATA_WIDTH),
.ADDR_WIDTH (ADDR_WIDTH)
)
controlor
(
.clock (clock),
.reset (reset),
.opcode (opcode),//来自解码器解码后指令
.s (s),//使能信号
.addr_sel (addr_sel),//程序或数据地址选通
.instrs (alu_oper) ); PC
#(.ADDR_WIDTH (ADDR_WIDTH))
pointer_counter
(
.clock (clock),
.reset (reset),
.en (s[]),
.pc (addr_pc)//code address
); memory
#(
.ADDR_WIDTH(ADDR_WIDTH),
.DATA_WIDTH (DATA_WIDTH)
)
memory
(
.clock (clock),
.reset (reset),
.wr_en (s[]),
.rd_en (s[] | s[]),
.addr (addr_mem),
.din (din_mem),
.dout (dout_mem)
); idec
#(
.DATA_WIDTH (DATA_WIDTH),
.ADDR_WIDTH (ADDR_WIDTH)
)
instr_decoder
(
.clock (clock),
.reset (reset),
.en (s[]),
.instruction(dout_mem),//from memory .opcode (opcode),
.addr (addr_idec)//data address
); alu
#(.OP_WIDTH(DATA_WIDTH))
alu
(
.clock (clock),
.reset (reset),
.en (s[]),
.add_en (alu_oper[]),//加法运算使能
.sub_en (alu_oper[]),
.and_en (alu_oper[]),
.pass_en (alu_oper[]),
.din (dout_mem),
.n (n),//负标志
.z (z),//0标志
.c (c),//输出进位标志
.v (v),//输出溢出标志
.a (din_mem)//累加器输出寄存器 dout ); endmodule

cpu_top.v

  现在仿真观察逻辑是否按照预期工作。这里使用Questasim工具,该工具的Windows/Linux版本都很容易下载到,而且对SV UVM支持程度高,是芯片自学的首选。只写了个简单的testbench来toggle clock和reset。

`timescale 1ns/1ps;

module tb_top;

    parameter T = ;

    logic clock;
logic reset;
logic n,z,c,v; initial begin:clock_toggle
clock = ;
forever begin
#(T/2.0);
clock = ~clock;
end
end initial begin
reset = ;
#;
reset = ;
#T;
reset = ;
#;
$stop;
end cpu_top DUT
(
.clock (clock),
.reset (reset),
.n (n),//负标志
.z (z),//0标志
.c (c),//输出进位标志
.v (v)//输出溢出标志
); endmodule

testbench.sv

  PC不断从0计数到5.每个计数周期内,各个模块的使能信号s也在交替拉高,指示当前进行不同的操作步骤。我们以第三个周期为例:

  s5:读取memory的'h1地址数据'b010_01100

  s4:得到8'h4c,解析出当前操作码是高三位3'h2(ADD),操作地址是第五位5'h0c

  s3:读取5'h0c地址内的数据'b0110_0101 即8'h65

  s2:调用ALU,将上次计算结果与当前读取memory中数据相加给din_mem。'h95+'h65='hfa

  s1:由于操作码不包括写入,当前时钟不操作

  s0:PC加1,为下一个指令周期做准备

  这个“CPU”真的简单到几乎不能做任何事情,但其对于初步接触的人还是很有帮助的。现代CPU指令集非常庞大,还包括一些寄存器、总线单元等专用硬件逻辑,所以要学的还有很多。从应用角度来讲,在更上一个层次掌握MCU的结构及原理更加重要。

代码中理解CPU结构及工作原理的更多相关文章

  1. 怎么理解impala(impala工作原理是什么)

    下面给大家介绍怎么理解impala,impala工作原理是什么. Impala是hadoop上交互式MPP SQL引擎, 也是目前性能最好的开源SQL-on-hadoop方案. 如下图所示, impa ...

  2. C51端口结构和工作原理(转)

    一.P0端口的结构及工作原理 P0端口8位中的一位结构图见下图:   由上图可见,P0端口由锁存器.输入缓冲器.切换开关.一个与非门.一个与门及场效应管驱动电路构成.再看图的右边,标号为P0.X引脚的 ...

  3. Java中的Annotation(2)----Annotation工作原理

    Java中的Annotation(2)----Annotation工作原理 分类: 编程语言2013-03-18 01:06 3280人阅读 评论(6) 收藏 举报 上一篇文章已经介绍了如何使用JDK ...

  4. NAND闪存颗粒结构及工作原理

    NAND闪存是一种电压元件,靠其内存电压来存储数据,现在我们就来谈谈它的结构及工作原理. 闪存的内部存储结构是金属-氧化层-半导体-场效晶体管(MOSFET),里面有一个浮置栅极(Floating G ...

  5. 1、cpu架构和工作原理

    cpu架构和工作原理 计算机有5大基本组成部分,运算器,控制器,存储器,输入和输出.运算器和控制器封装到一起,加上寄存器组和cpu内部总线构成中央处理器(CPU).cpu的根本任务,就是执行指令,对计 ...

  6. CPU中断的工作原理,从最底层讲起

    前言 中断的概念属于硬件层.虽然我们在进行软件编程时不会直接使用中断,但理解它对我们来说依然重要. 我们在使用线程切换及状态管理.异常处理.硬件与处理器的交互.I/O操作等指令时,中断都在默默的为我们 ...

  7. CPU GPU设计工作原理《转》

    我知道这非常长,可是,我坚持看完了.希望有幸看到这文章并对图形方面有兴趣的朋友,也能坚持看完.一定大有收获.毕竟知道它们究竟是怎么"私下勾搭"的.会有利于我们用程序来指挥它们... ...

  8. 8. 理解ZooKeeper的内部工作原理

    到目前为止,我们已经讨论了ZooKeeper服务的基础知识,并详细了解了数据模型及其属性. 我们也熟悉了ZooKeeper 监视(watch)的概念,监视就是在ZooKeeper命名空间中的znode ...

  9. FPAG结构 组成 工作原理 开发流程(转)

    FPGA组成.工作原理和开发流程 备注:下面的描述基于ALTERA系列的FPGA芯片,而且是第一次学习FPGA,其中的一部分内容是参考一些资料总结的,个人独特的分析和见解还偏少. 1. FPGA概述 ...

随机推荐

  1. 苹果笔记本修改pycharm for mac 修改字体大小

    实在是隐藏的太深了,无语

  2. nmap基本命令使用

    nmap 是主机探测.端口扫描.版本检测.系统检测.支持探测脚本编写.查看那个端口和开着啥 telnet 8.8.8.8 可以查看ip nmap ip -p<port> 根据常用服务猜测他 ...

  3. 以windows服务方式快速部署免安装版Postgres数据库

    目录 以windows服务方式快速部署免安装版Postgres数据库 1.下载Postgresql数据库免安装包 2.安装环境准备及验证 解压文件 测试环境依赖 3.创建并初始化数据目录 创建数据目录 ...

  4. DEVOPS技术实践_19:Pipeline的多参数json调用

    在上一篇学习了把参数写进Json文件,然后通过去Json文件,调用参数的方法 1. 三元运算符介绍 调用的方法是通过一个三元运算符实现的 gender = prop.GENDER? prop.GEND ...

  5. 1040 有几个PAT (25 分)C语言

    字符串 APPAPT 中包含了两个单词 PAT,其中第一个 PAT 是第 2 位§,第 4 位(A),第 6 位(T):第二个 PAT 是第 3 位§,第 4 位(A),第 6 位(T). 现给定字符 ...

  6. centos7下图形界面和命令行界面切换

    在图形界面使用 ctrl+alt+F2切换到dos界面 dos界面 ctrl+alt+F2切换回图形界面 在命令上 输入 init 3 命令 切换到dos界面 输入 init 5命令 切换到图形界面 ...

  7. schedule of 2016-10-31~2016-11-6(Monday~Sunday)——1st semester of 2nd Grade

    most important things to do 1.joint phd preparations 2.journal paper to write 3.solid fundamental kn ...

  8. DHCP服务器搭建

    一.服务端安装配置 1.安装dhcp相关软件包 执行命令:yum install dhcp dhcp-devel -y #通过yum安装dhcp软件包 2.编辑配置dhcp的配置文件,文件路径:/et ...

  9. 详解定时任务中的 cron 表达式

    1.前言 我们经常使用 cron 表达式来定义定时任务的执行策略,今天我们就总结一下 cron 表达式的一些相关知识. 2. cron 表达式的定义 cron 表达式是一个字符串,该字符串由 6 个空 ...

  10. 使用“1”个参数调用“DownloadString”时发生异常:“操作超时”

    我今天在终端美化时间遇到一个问题是这样的 使用“1”个参数调用“DownloadString”时发生异常:“操作超时” 然后网我看了下,访问链接属于https的东西,根据直觉我觉得是这样的,是由于访问 ...