冯诺依曼CPU实现基础知识预备(一)

本文深入探讨了冯诺依曼架构下CPU的基础实现,包括文件操作、数据读取、二进制与十进制转换、位运算、补码表示、运算符使用等关键概念。通过实例讲解了原码、反码、补码的转换及其在加减运算中的应用,以及位运算符在实际编程中的具体运用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

冯诺依曼CPU实现基础知识预备(一)

文件操作

FILE* fopen fclose
从文件读入数据fgets(buffer [ ],length, file*)
读入时将二进制数据8位为一组转为十进制存入char类型的主存

//char于c语言中为一字节,为8bit(1B(byte,字节)= 8 bit)

二进制所涉及的位运算

(本部分来自学校ppt内容及其他四处搜刮的内容)

一、机器数

1.计算机中数的正负表示:二进制最高位表示符号。
最高位为1,则为负;为0,则是负数。
2.增加了符号位的二进制数为机器数,数值称为机器数的真值
3.编码格式:原码表示法、反码表示法、补码表示法

(C Prime Pus 第六版)
15.1.2 有符号整数
如何表示有符号整数取决于硬件,而不是C语言。也许表示有符号数最简单的方式是用1位(如,高阶位)储存符号,只剩下7位表示数字本身(假设储存在1字节中)。用这种符号量(sign-magnitude) 表示法,10000001 表示-1,00000001 表示1。因此,其表示范围是-127~+127.
这种方法的缺点是有两个0: +0 和-0。这很容易混淆,而且用两个位组合来表示一一个值 也有些浪费。
二进制补码(two‘s-complement)方法避免了这个问题,是当今最常用的系统。我们将以1字节为例讨论这种方法。
二进制补码用1字节中的后7位表示0~127,高阶位设置为0。目前,这种方法和符号量的方法相同。另外,如果高阶位是1,表示的值为负。
这两种方法的区别在于如何确定负值

第一种即为原码表示法,缺点如下
1.有两个0
2.只支持两个正数的加法运算

例:正数和负数的原码相加: 43 + (-43)
X = (+43)10,[ X ]原码 = 0 0101011
Y = (-43)10,[ Y ]原码 = 1 0101011
X + Y:
:0 0101011
+ 1 0101011
= 1 1010110
= -86

于是数学家们提出了补码这一概念,为了把减法变成加法运算
下面我们会进一步梳理补码的产生过程

来源于上课所用ppt

1.补数

(1)补数的概念: 设X是N进制数,则 |X的补数|=N-|X|,X的补数的正负和X正好相反 例如: X是12进制数,X=5,则X的补数是 -7;
X是128进制数,X=-15,则X的补数是113;

(2)变减为加原理--时钟的例子:
时钟的6点减去8个小时 ,相当于将时钟逆时针调8个小时,结果是10点 ;时钟是圆形的,逆时针调8个小时 = 顺时针调4个小时。逆时针调是减法,顺时针调是加法。
结论:在时钟的12进制上, 6-8 = = 6+4 而-8和+4在模12上互为补数 。 因此 6-8= = 6+(-8的补数) 从而实现了变减为加

(3)正式得出
思路:对于2进制数X和Y,X-Y=X+(-Y的补数),从而变减法为加法! 同理,N位二进制数的模是2N,两个N位二进制正数X和Y相减,即X-Y,都可以转换成X
+(-Y的补数),(-Y的补数)是正数,等于模数2N-Y。 例: 模 28=256
-1的补数就是255, 1+255=0; 2+(-1)=2+255=1;

0至127都表示正整数,128至255都表示负数的补数。 推广:
32位长的整数,最高位表示正负,也代表数值,0至231-1都表示正数,231至232-1都表示负数的补数.

2、反码

为了生成补码,负数的原码必须经过转换,这个转换过程中需要引入反码 。 正数的反码表示与原码相同。 负数的反码表示,是原码符号位不变,其他位按位取反形成的。 例如:X = (-43)10
[ X ]原码 = 1 0101011
[ X ]反码 = 1 1010100

3、补码

正数的补码表示与原码相同。 负数的补码,是负数的反码在最低位加1得到的结果。 例如: X = (-43)10
[ X ]原码 = 1 0101011
[ X ]反码 = 1 1010100
[ X ]补码 = 1 1010101

现在来看个小例子
例:92 - 100
X = +92,[ X ]补码 = 0 1011100
Y = 100,-Y=-100 = 1 1100100
[ -Y ] 补码 = 1 0011100

[X - Y] 补码= [ X ] 补码 + [ -Y ] 补码:
0 1011100
+ 1 0011100
1 1111000
即[ X - Y] 补码 = 1 1111000

所以[ X - Y] 原码 = [ [ X - Y ] 补码 ] 补码
= 1 0001000=-8
即 92 - 100 = -8

(续c prime plus)
从一个9位组合100000000 (256 的二进制形式)减去一个负数的位组合,结果是该负值的量。例如,假设一个负值的位组合是1000000,
作为一个 无符号字节,该组合为表示128:作为一个有符号值,该组合表示负值(编码是7的位为1),而且值为100000000—10000000,即1000000 ( 128)。因此,该数是-128 (在符号量表示法中,该位组合表示-0)。
类似地,10000001 是-127,11111111 是-1。该方法可以表示-128~+127范围内的数。
要得到一个二进制补码数的相反数,最简单的方法是反转每一位(即0变为1, 1变为0),然后加1。
因为1是0000001,那么-1则是1111110+1,或111111。这与上面的介绍一致。 二进制反码(one’s-complement)方法通过反转位组合中的每一“位形成- -个负数。例如,0000001是1,那么1111110是-1。这种方法也有一个-0:111111。 该方法能表示- 127~+127之间的数。

2.运算符及实际运用

(1)运算符的介绍

C语言提供了六种位运算符:
& 按位与 全1为1,遇0则0
| 按位或 全0才0,遇1则1
^ 按位异或 相同为0,不同为1
~ 取反 0变1,1变0
<< 左移
>> 右移

关于左移右移的补充说明

以右移为例(摘于ppt)
右移运算符用法 n>>m 功能是把数n的各位右移m位。常用于将低m位去除,或者将m+1位取出。
例如:设a=15,a>>3表示把000001111右移为00000001(仅保留第4位以上)。
对于正数,高位补0。
对于负数,某些机器将对左边空出的部分用1填补(即“算术移位”)
而另一些机器则对左边空出的部分用0填补(即“逻辑移位”)。

关于|与^的补充说明

按位或运算符“|”是双目运算符。其功能是参与运算的两数各对应的二进位相或。只要对应的二个二进位有一个为1时,结果位就为1。参与运算的两个数均以补码出现。
例如:9 | 5
00001001
| 00000101
00001101 (十进制为13)
按位或运算常用来对一个数据的某些位定值为1。例如:如果想使一个数a的最低位改为1,则只需要将a与1进行按位或运算即可。

具体示例

>  //从低位到高位数,将n的第m位置1。 
>  int setBitToOne(int n, int m)
>  {  
>     return n | (1<<(m-1));  
>     /*将1左移m-1位找到第m位,得到000...1...000, 
>       n和这个数做或运算*/   
>  }
>    //从低位到高位数,将n的第m位置0。  
>    int setBitToZero(int n, int m)
>    {  
>     return n & ~(1<<(m-1));  
>     /* 将1左移m-1位找到第m位,取反后变成111...0...1111 , n再和这个数做与运算*/   
>     } 
>     //从低位到高位数,将字节byte的第m位置1。 
>     char setBitToOne(char byte, int m)
>     {  
>     return byte | (1<<(m-1));  
>     /*将1左移m-1位找到第m位,得到000...1...000,  	字节byte和这个数做或运算*/  
>      } 
//从低位到高位数,将字节byte的第m位置0。  
char setBitToZero(char byte, int m)
{  
>     return byte & ~(1<<(m-1));  
>     /* 将1左移m-1位找到第m位,取反后变成111...0...1111 ,字节byte再和这个数做与运算*/   
}   
//将8位的0/1字符串,存入二进制表示的一个字节。 
 char binaryToByte(const char str[])
 { 	
	 int i; 	
	 char byte=0; 	 	
	 for (i=0;i<8;i++)
	 { 		
		 if (str[i]=='1')
		 byte=setBitToOne(byte,8-i); 	
	} 	
	return (byte);
 }

按位异或运算符“^”是双目运算符。其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。参与运算的两个数均以补码出现。
例如:9^ 5
00001001
^ 00000101
00001100 (十进制为12)

例如:交换两个值,不用临时变量,想将a和b的值互换,可以用以下赋值语句实现:
a=a∧b;
b=b∧a;
a=a∧b;

例如:
a=3;b=4。
a=011∧100=111(a∧b的结果,a已变成7)
b=100 ∧ 111=011(b∧a的结果,b已变成3)
a =111 ∧011=100(a∧b的结果,a已变成4)

关于复合运算说明 位运算符与赋值运算符可以组成复合赋值运算符。例如: &=, |=, >>=, <<=, ∧=

a & = b相当于 a = a & b a << =2相当于a = a << 2
a ∧= b相当于a = a ∧ b

void swap(int *a,int *b)
{     
    (*a)^=(*b);
	(*b)^=(*a);
	(*a)^=(*b);
     //(*a)^ =(*b)^ =(*a)^ =(*b);   
}

左移1位相当于该数乘以2,
左移2位相当于该数乘以22=4,
例:15<<2=60,即乘了。
int mulTwo(int n)
{
//计算n
2
return n<<1;
}
int getFactorialofTwo(int n)
{
//n > 0
return 2<<(n-1);//2的n次方
}

int divTwo(int n)
{
//负奇数的运算不可用
return n>>1;//除以2
}

int divTwoPower(int n,int m)
{
//计算n/(2^m)
return n>>m;
}

int isOddNumber(int n){
return (n & 1) == 1;
}

int getMaxInt()
{
return (1<<(sizeof(int)*8-1)) - 1;
//由于优先级关系,括号不可省略
}

int getMinInt()
{
return 1<<(sizeof(int)*8-1)); //-2147483648
}

(2)实际运用

//此代码为得出整数对应的16位二进制数
#include <stdio.h>
int getBit (int n, int m); 
int main( ) 
{
	int a,bitlen,i,pow,digit;
	scanf("%d",&a);
	bitlen=sizeof(int)*8;
	for (i=bitlen;i>0;i--)
	{
		printf("%d ",getBit(a,i));
	}
	return 0;		
}
//从低位到高位,取n的第m位 
int getBit(int n, int m){  
    return (n >> (m-1)) & 1;  
}  
//输入3,则求-3的补码
#include <stdio.h>
int getBit(int n, int m); 
int main(){
	int a,bitlen,i,pow,digit,b;
	scanf("%d",&a);
    b= ~a+1;
	printf("-%d=%d\n",a,b);
	bitlen=sizeof(int)*8;
	for (i=bitlen;i>0;i--){
	   printf("%d ",getBit(b,i));
	}
	return 0;		
}
//从低位到高位,取n的第m位 
int getBit(int n, int m){  
    return (n >> (m-1)) & 1;  
}  
//计算拆解
#include <stdio.h>
int getBit(int n, int m); 
void bitstring(int n);
int main(){
	int a,b,c;
	scanf("%d %d",&a,&b);
	bitstring(a);
	bitstring(b);
	printf("-----------------------\n");
	c=a+b;
	bitstring(c);
	printf("%d+%d=%d\n",a,b,c);
	return 0;		
}
int getBit(int n, int m){  
    return (n >> (m-1)) & 1;  
} 
void bitstring(int n)
{	int bitlen,i;
	bitlen=sizeof(int)*8;
	for (i=bitlen;i>0;i--)
	{
	printf("%d ",getBit(n,i));
	}
	printf("\n");
} 
//加法器模拟
#include<stdio.h> 
int getBit(int n, int m); 
int main(){
	int a,b,c,bitlen,i,x,y,s;
	scanf("%d %d",&a,&b);
	bitlen=sizeof(int)*8;
	for (i=1,s=0,c=0;i<=bitlen;i++){
		x=getBit(a,i);   
		y=getBit(b,i);
		s += (x^y^c)<<(i-1);
		printf("%d ",x^y^c);
		if (c==0)  c=x&y;
		else c=(x|y)&c;
    }	printf("\n");
	printf("%d+%d=%d\n",a,b,s);
	return 0;		
}
int getBit(int n, int m){  
    return (n >> (m-1)) & 1;  
} 

OVER

要求及分析

1.要求

任务概述:
模拟一个简易的冯诺依曼式计算机CPU的工作。
该CPU字长为16位,共11个寄存器,其中3个系统寄存器,分别为程序计数器,指令寄存器,标志寄存器;8个通用寄存器,即寄存器1、2、3、4(数据寄存器),寄存器5、6、7、8(地址寄存器)。该CPU至多支持32K内存。内存分两部分,一部分为代码段,从地址0开始。另一部分为数据段,从地址16384开始。其所支持的指令集如表1-1。每条指令固定由32位(编号为0到31)二进制数组成,其中第0到7位为操作码,代表CPU要执行哪种操作;第8到15位为操作对象,如寄存器,内存地址等;第16到31位为立即数。该CPU有一个输入端口和一个输出端口。输入端口的数据由标准输入设备(键盘)输入,输出端口的数据输出到标准输出设备(显示器)上。

程序的大致步骤:
程序开始时要从指定文件中读入一段用给定指令集写的程序至内存(从地址0开始顺序保存),程序计数器初始值也为0。然后程序就开始不断重复取指令(读取程序计数器PC内的指令地址,根据这个地址将指令从内存中读入,并保存在指令寄存器中,同时程序计数器内容加4,指向下一个条指令。因为我们所有的指令长度固定为4个字节,所以加4)。分析指令(对指令寄存器中的指令进行解码,分析出指令的操作码,所需操作数的存放位置等信息)和执行指令(完成相关计算并将结果写到相应位置)的过程。程序每执行一条指令就要输出CPU当前的状态,如各寄存器的值,指定内存的值等(具体要求在后边)。当读到停机指令时,程序按要求输出后就结束了。

2.分析

大过程分析

在这里插入图片描述

指令集分析

在这里插入图片描述
写不动了。。。自己一边学一边写吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值