冯诺依曼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 10101003、补码
正数的补码表示与原码相同。 负数的补码,是负数的反码在最低位加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)
{
//计算n2
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.分析
大过程分析
指令集分析
写不动了。。。自己一边学一边写吧