标题:不等了。关于CPU模拟的解决方案发布 及 散分!
取消只看楼主
beyondyf
Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19
等 级:贵宾
威 望:103
帖 子:3282
专家分:12654
注 册:2008-1-21
结帖率:100%
已结贴  问题点数:100 回复次数:7 
不等了。关于CPU模拟的解决方案发布 及 散分!
本来是clcqifeng问T、P版的问题。不过P版好像不在,T版一如既往的不知所云。
之后Z版的参与激发了我的兴趣。在上学时考过高级程序员。其中要考一个叫CASL的汇编语言,是用在一台叫COMET的抽象计算机上的。
当时这个东西让我觉得很扯,抽象计算机,只是个概念。学着学着就动了个念头,为什么不实现一个虚拟的COMET来实际运行一下?
于是我写了一个COMET虚拟机,以及CASL编译器。用来编译执行用CASL写的汇编代码。
呵呵,昨天我试着找了找看能不能找到我的COMET,可惜没找到,我电脑都换了三台了。

本来一直在等Z版的方案。不过看起来Z版要打长久战了。呵呵,恕我直言,我觉得Z版的解决方案有点复杂了,别介意我这么说,请按照你的思路完成下去。

我的思路很简单,建立这个CPU的硬件执行环境的模型,用软件模拟硬件的工作过程。所以我的代码是建立一个虚拟机。
指令集也是开放式的,可以随意定义和修改。

由于只测试了原楼主的两个示例,不排除里面存在缺陷。
欢迎各位下载源代码编译测试。有什么问题或意见欢迎跟贴讨论。

程序代码:
/***************************************************************
r1 r2 r3

0001        复制操作
00010011    复制r3到r2
00010101    复制r3到r1
00010110    复制r2到r1
00011011    复制r2到r3
00011101    复制r1到r3
00011110    复制r1到r2

0010        加法
00100011    将r2与r3相加,结果存放在r2
00100101    将r1与r3相加,结果存放在r1
00100110    将r1与r2相加,结果存放在r1
00101011    将r3与r2相加,结果存放在r3
00101101    将r3与r1相加,结果存放在r3
00101110    将r2与r1相加,结果存放在r2

0011        减法
00110011    用r2减去r3,结果存放在r2
00110101    用r1减去r3,结果存放在r1
00110110    用r1减去r2,结果存放在r1
00111011    用r3减去r2,结果存放在r3
00111101    用r3减去r1,结果存放在r3
00111110    用r2减去r1,结果存放在r2

0100        移动到register
01000011    将r3中存放的地址的数据放到r2中
01000101    将r3中存放的地址的数据放到r1中
01000110    将r2中存放的地址的数据放到r1中
01001011    将r2中存放的地址的数据放到r3中
01001101    将r1中存放的地址的数据放到r3中
01001110    将r1中存放的地址的数据放到r2中
01000100 xxxxxxxx    数字X放到r1中
01000010 xxxxxxxx    数字X放到r2中
01000001 xxxxxxxx    数字X放到r3中

0101        移动到memory
01010011    将r2中存放的数据放到r3所指的内存地址中去
01010101    将r1中存放的数据放到r3所指的内存地址中去
01010110    将r1中存放的数据放到r2所指的内存地址中去
01011011    将r3中存放的数据放到r2所指的内存地址中去
01011101    将r3中存放的数据放到r1所指的内存地址中去
01011110    将r2中存放的数据放到r1所指的内存地址中去

0110        为零跳转
01100011    如果r2的值为零,则跳转到r3所指的代码处
01100101    如果r1的值为零,则跳转到r3所指的代码处
01100110    如果r1的值为零,则跳转到r2所指的代码处
01101011    如果r3的值为零,则跳转到r2所指的代码处
01101101    如果r3的值为零,则跳转到r1所指的代码处
01101110    如果r2的值为零,则跳转到r1所指的代码处

0111        不为零跳转
01110011    如果r2的值不为零,则跳转到r3所指的代码处
01110101    如果r1的值不为零,则跳转到r3所指的代码处
01110110    如果r1的值不为零,则跳转到r2所指的代码处
01111011    如果r3的值不为零,则跳转到r2所指的代码处
01111101    如果r3的值不为零,则跳转到r1所指的代码处
01111110    如果r2的值不为零,则跳转到r1所指的代码处

1000        打印
10000100    打印r1所指向的内存地址的数字
10000010    打印r2所指向的内存地址的数字
10000001    打印r3所指向的内存地址的数字
10001100    打印r1所指向的内存地址的字符串,字符串以0结尾
10001010    打印r2所指向的内存地址的字符串,字符串以0结尾
10001001    打印r3所指向的内存地址的字符串,字符串以0结尾

11110000    程序结束
***************************************************************/
#include<stdio.h>
#include<string.h>

#define VM_MEMORY_SIZE    0xFF

#define VM_END        0
#define VM_RUN        1
#define VM_ERR        2

typedef struct
{
    unsigned char pc;
    unsigned char r1;
    unsigned char r2;
    unsigned char r3;
    unsigned char code[VM_MEMORY_SIZE];
    unsigned char data[VM_MEMORY_SIZE];
}VM;

void vm_initialize(VM * virtual_machine);
int vm_single_step(VM * virtual_machine);
int vm_run(VM * virtual_machine, char * code, int code_length);
int str_to_code(char * str, char * code);

int main()
{
    VM vm;
    char str[2048];
    char code[256];
    int len, ret;
    for(;;)
    {
        printf("VM > ");
        fgets(str, 2048, stdin);
        len = str_to_code(str, code);
        if(len == 0) break;
        ret = vm_run(&vm, code, len);
        switch(ret)
        {
            case VM_END: printf("\nOVER\n"); break;
            case VM_ERR: printf("\nERROR\n"); break;
        }
    }
    return 0;
}

void vm_initialize(VM *vm)
{
    int i;
    vm->pc = 0;
    vm->r1 = 0;
    vm->r2 = 0;
    vm->r3 = 0;
    for(i = 0; i < VM_MEMORY_SIZE; i++)
    {
        vm->code[i] = 0;
        vm->data[i] = 0;
    }
}

int vm_single_step(VM *vm)
{
    int code;
    code = vm->code[vm->pc++];
    switch(code)
    {
        case 0x13: vm->r2 = vm->r3; break;
        case 0x15: vm->r1 = vm->r3; break;
        case 0x16: vm->r1 = vm->r2; break;
        case 0x1B: vm->r3 = vm->r2; break;
        case 0x1D: vm->r3 = vm->r1; break;
        case 0x1E: vm->r2 = vm->r1; break;

        case 0x23: vm->r2 += vm->r3; break;
        case 0x25: vm->r1 += vm->r3; break;
        case 0x26: vm->r1 += vm->r2; break;
        case 0x2B: vm->r3 += vm->r2; break;
        case 0x2D: vm->r3 += vm->r1; break;
        case 0x2E: vm->r2 += vm->r1; break;

        case 0x33: vm->r2 -= vm->r3; break;
        case 0x35: vm->r1 -= vm->r3; break;
        case 0x36: vm->r1 -= vm->r2; break;
        case 0x3B: vm->r3 -= vm->r2; break;
        case 0x3D: vm->r3 -= vm->r1; break;
        case 0x3E: vm->r2 -= vm->r1; break;
       
        case 0x41: vm->r3 = vm->code[vm->pc++]; break;
        case 0x42: vm->r2 = vm->code[vm->pc++]; break;
        case 0x43: vm->r2 = vm->data[vm->r3]; break;
        case 0x44: vm->r1 = vm->code[vm->pc++]; break;
        case 0x45: vm->r1 = vm->data[vm->r3]; break;
        case 0x46: vm->r1 = vm->data[vm->r2]; break;
        case 0x4B: vm->r3 = vm->data[vm->r2]; break;
        case 0x4D: vm->r3 = vm->data[vm->r1]; break;
        case 0x4E: vm->r2 = vm->data[vm->r1]; break;

        case 0x53: vm->data[vm->r3] = vm->r2; break;
        case 0x55: vm->data[vm->r3] = vm->r1; break;
        case 0x56: vm->data[vm->r2] = vm->r1; break;
        case 0x5B: vm->data[vm->r2] = vm->r3; break;
        case 0x5D: vm->data[vm->r1] = vm->r3; break;
        case 0x5E: vm->data[vm->r1] = vm->r2; break;

        case 0x63: if(vm->r2 == 0) vm->pc = vm->r3; break;
        case 0x65: if(vm->r1 == 0) vm->pc = vm->r3; break;
        case 0x66: if(vm->r1 == 0) vm->pc = vm->r2; break;
        case 0x6B: if(vm->r3 == 0) vm->pc = vm->r2; break;
        case 0x6D: if(vm->r3 == 0) vm->pc = vm->r1; break;
        case 0x6E: if(vm->r2 == 0) vm->pc = vm->r1; break;

        case 0x73: if(vm->r2 != 0) vm->pc = vm->r3; break;
        case 0x75: if(vm->r1 != 0) vm->pc = vm->r3; break;
        case 0x76: if(vm->r1 != 0) vm->pc = vm->r2; break;
        case 0x7B: if(vm->r3 != 0) vm->pc = vm->r2; break;
        case 0x7D: if(vm->r3 != 0) vm->pc = vm->r1; break;
        case 0x7E: if(vm->r2 != 0) vm->pc = vm->r1; break;
       
        case 0x81: printf("%d", (char)vm->data[vm->r3]); break;
        case 0x82: printf("%d", (char)vm->data[vm->r2]); break;
        case 0x84: printf("%d", (char)vm->data[vm->r1]); break;
        case 0x89: printf("%s", vm->data + vm->r3); break;
        case 0x8A: printf("%s", vm->data + vm->r2); break;
        case 0x8C: printf("%s", vm->data + vm->r1); break;
       
        case 0xF0: vm->pc--; return VM_END;
       
        default: vm->pc--; return VM_ERR;
    }
    return VM_RUN;
}

int vm_run(VM *vm, char *code, int len)
{
    int i, j;
    vm_initialize(vm);
    for(i = 0; i < len && code[i] != (char)0xF0; i++) vm->code[i] = code[i];
    if(i < len && code[i] == (char)0xF0) vm->code[i] = 0xF0;
    for(i++, j = 0; i < len; i++, j++) vm->data[j] = code[i];
   
    while((i = vm_single_step(vm)) == VM_RUN);
    return i;
}

int str_to_code(char * str, char * code)
{
    char delimiters[] = " \r\n";
    char * token;
    int i, len = 0;
    for(token = strtok(str, delimiters); token != NULL; token = strtok(NULL, delimiters))
    {
        for(i = 0; *token == '0' || *token == '1'; token++)
            i = (i << 1) + *token - '0';
        code[len++] = i;
    }
    return len;
}
收到的鲜花
  • pangding2012-03-12 23:14 送鲜花  10朵   附言:你要乐意可以给自己加个精,方便后人发现这 ...
搜索更多相关主题的帖子: 高级程序员 计算机 虚拟机 编译器 三台 
2012-03-10 23:10
beyondyf
Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19
等 级:贵宾
威 望:103
帖 子:3282
专家分:12654
注 册:2008-1-21
得分:0 
回复 BlueGuy

void vm_initialize(VM * virtual_machine);
int vm_single_step(VM * virtual_machine);
int vm_run(VM * virtual_machine, char * code, int code_length);
以及VM结构,共同构成了虚拟机的整体

data code模拟两段内存,一段存数据,一段存代码。至于为什么这么做请参见原贴示例。
合并在一起也不是不可以。方法有增加段基址寄存器、扩大指令计数器范围等等。总之就是区分开数据段与代码段。

不好意思,需要出去办点事,晚上回来再聊

重剑无锋,大巧不工
2012-03-11 08:10
beyondyf
Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19
等 级:贵宾
威 望:103
帖 子:3282
专家分:12654
注 册:2008-1-21
得分:0 
回复 8楼 你们都要疼我哦
指令不分离出来,不对二进制opcode进行解析。。。这样的实现,似乎有点取巧,
 谈不上“模拟”cpu。
 至于说虚拟机。。。 虚拟的太上层了

饿,不知兄台测试过我的代码没。我想说的是,不明白你的意思。你想怎么分离指令?
兄台在这个问题的原贴中有过回复,想必看过原贴主人的要求了。我的程序忠实地完成他的要求。opcode不解析怎么能完成要求呢。
至于设计方案,这个见仁见智了。我很满意我的方案。原本也想过设计个指令矩阵,后来觉得即使现在最高端的CPU也不过几百条指令(包括各种寻址方式),全用CASE也不是什么复杂的事情。所幸返璞归真,这样每条指令我可以在一个独立的CASE里安排它的执行逻辑,很方便。

模拟CPU——这一词是原贴主人用的,我只是延用而已。其实模拟的是一台拥有这样CPU的小系统,当是个MCU吧。

说它是虚拟机倒并不为过。它接收指令,并按指令执行出结果。

兄台有不同意见是件好事,不过说的有点含混,我不理解你想表达的具体意思。有兴趣,不妨也写一段代码来交流一下。

重剑无锋,大巧不工
2012-03-11 19:25
beyondyf
Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19
等 级:贵宾
威 望:103
帖 子:3282
专家分:12654
注 册:2008-1-21
得分:0 
回复 13楼 BlueGuy
呵呵,你我又没什么利益纠葛,我哪给你的压力啊?

你来探讨我挺开心的。你也看过我的书架了,上层就有两本关于编译原理的书。

不过,就这个方案来说,跟编译其实没多大关系。原贴主人要求的输入就是机器码,所以我的侧重点是模拟指令的执行。

指令的解析过程就放在str_to_code函数里。我希望突出重点,所以这里没有下过多的功夫。因为这个虚拟机是给会用它的人用的,不是谁拿着都能当游戏玩的。

zaixuexi怎么结贴了?他可能有自己的想法,想把这个东西做的更大,更通用。从他的代码中我看到了缓存和页式存储的标识。
呵呵,我想他可能迷失了。什么东西都要有个度。不管怎么做,这个被模拟的CPU只有3个8位的寄存器而已。
如果要扩大范围,那被模拟的就不是这个CPU了。

BlueGuy还有那位你们都要疼我哦版主,有兴趣也可以做一个类似的试试来和大家交流一下工程思想。

当然,这是个邀请,决不是挑衅。不想玩也没关系,我们都是在业余时间来这里交流的,又不是工作,别有什么压力

重剑无锋,大巧不工
2012-03-11 19:49
beyondyf
Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19
等 级:贵宾
威 望:103
帖 子:3282
专家分:12654
注 册:2008-1-21
得分:0 
强大谈不上,只是一时兴起做的一个玩具。代码的价值不在于实用,而在于其中包含的一点个人的思想。

重剑无锋,大巧不工
2012-03-11 20:13
beyondyf
Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19
等 级:贵宾
威 望:103
帖 子:3282
专家分:12654
注 册:2008-1-21
得分:0 
看来也没有更多的交流了。这就结贴了。

重剑无锋,大巧不工
2012-03-12 18:54
beyondyf
Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19
等 级:贵宾
威 望:103
帖 子:3282
专家分:12654
注 册:2008-1-21
得分:0 
回复 29楼 你们都要疼我哦
终于知道你想表达的意思了。

谈一下我的观点吧。我的模拟是相当底层的模拟。single_step这个函数完成的是该CPU一个机器周期的工作。

code = vm->code[vm->pc++]; 模拟取指周期
switch(code)    即译码过程
case 0xXX    模拟执行周期

SWITCH中每一个CASE对应相应指令的动作步骤。它相当于CPU中的控制部分(指令译码器和控制矩阵)。这里我要纠正一个概念,我的这个函数做的不是什么整体转换,是执行指令。这个其实写成对象模型就更形象了。

建议参考微机原理的相关部分,看一下处理器的译码器电路执行原理。

这里我提个问题。你把操作码解析出来的目的是什么?为什么要解析出是操作指令、操作数、寻址方式这些东西?

你想要的解析结果只是符合人的思维习惯,但实现来说只会增加代码的复杂程度,并没有别的积极的改善(理解起来也未必轻松)。关于这一点光靠嘴说是没用的。如果你有成熟的方案我非常希望你能实现成代码。这不是在比试什么,我也不是在挑衅你。我想借鉴一下你的思想,而理解你编程思想的最直接的方法当然是看你的代码。

我觉得你搞混了编译器和虚拟机的差别。编译器是将一种指令系统转化为另一种指令系统。在这个过程中才需要对前一种指令进行解析,分离出各种元素,然后再合成为另一种指令系统。而虚拟机则是用于在一个系统上执行另一个系统的指令序列。

重剑无锋,大巧不工
2012-03-13 12:54
beyondyf
Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19
等 级:贵宾
威 望:103
帖 子:3282
专家分:12654
注 册:2008-1-21
得分:0 
回复 30楼 clcqifeng
在你的原贴中我已经说过了,我认为你示例2的第3个字节敲错了。不是10000001,而是10001001。

这一点请你和你们头头核实,最好让他核实一遍我发的指令集是否符合要求。有不同意见请反馈回来。

我没想到你的水平还没达到入门的阶段。并没有指责你的意思,只想说,暂时你还做不了这样的程序,解释你也听不懂的。还是静下心来先把基础打扎实了。

重剑无锋,大巧不工
2012-03-13 13:04



参与讨论请移步原网站贴子:https://bbs.bccn.net/thread-362783-1-1.html




关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.751423 second(s), 9 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved