了解指令如何翻译(计算机体系结构)

时间:2013-04-18 12:22:02

标签: assembly embedded emulation computer-architecture 6502

有点令人困惑的问题。但我真的在寻找学习一些低级编程。事情是,开发板像Arduino / Etc。真的隐藏了很多事情。

我花了一些时间学习计算机体系结构,逻辑/门/顺序逻辑/等等。(我甚至去了解与此相关的半导体和电子物理学,只是为了知道到底发生了什么,以及如何使用CMOS晶体管等制造盖茨。

但那就是它结束的地方......我希望能够理解一个指令(如Hex /或汇编/等等代码)是如何通过一台简单的计算机(很多书我的'使用过直接从盖茨到计算机....没有真正的中间)。甚至是简单的东西.....将值存储在寄存器或内存位置(也可能打印到像素?或其他东西)。

我认为最有趣的事情可能是最终编写模拟器。我有高级语言的经验,但是我听说像6502这样的东西可能是一个好的开始,因为你使用了很多汇编,并且指令集不是太大。

有谁知道任何可能有帮助的资源/想法/书籍?我已经阅读了“计算系统的元素”,虽然......这是一本很好的书,我真的不觉得它真的发生了什么,看到它发生了。这可能更像是一个Electronics.stackexchange问​​题,如果是这样,我道歉。

6 个答案:

答案 0 :(得分:6)

概述

你真的有很多选择。我将总结我对如何翻译指令的看法,但我还提供了一些我在开始使用时的选项。

我的选择

首先,从二进制输入的角度来看,它最容易思考。我们假设你有一个16位微处理器。 (即,指令以16位二进制位编码。)考虑将数字放入寄存器的汇编操作SET。例如:

SET(R1, 12) // Stores 12 into register 1

让我们随意(因为即使在任何标准架构中,选择都是任意的)选择SET指令转换为以下16位二进制值I:

0001 0001 0000 1100

基本上,我刚刚制定了一个公约。但这是我如何分解它。我选择让位I [15:12](用big-endian符号表示)表示特定指令。我选择让整数1对应于指令SET。现在我已经决定了这个约定,我可以说如果我有一个SET指令,让I [11:8]位对应寄存器。 (显然这意味着我只有16个寄存器:4 ^ 2 = 16)。最后,我让I [7:0]位对应于我想要存储在给定寄存器中的数据。让我们再次以二进制形式查看SET(R1,12)(为清晰起见,我将每组四个用换行符分开):

if I =  0001 0001 0000 1100
I[15:12] = 0001 (binary) = 1 (decimal) = SET instruction
I[11:8] = 0001 (binary) = 1 (decimal) = R1 since I[15:12] correspond to SET.
I[7:0] = 0000 1100 (8-bit binary) = 12 (decimal) = value to store in R1.

如您所见,微处理器中的其他所有内容都变得非常简单。让我们说你的商店在RAM中存储了4行指令。你有一个附在时钟上的计数器。计数器通过RAM中的行计数。当时钟"滴答"公羊的新指令出来了。 (即 next 指令来自RAM - 尽管在插入JUMP语句时这可能有点武断。)RAM的输出通过多个位选择器。您选择I [15:12]位并将它们发送到控制单元(CLU),它告诉您您要传达的指令。即SET,JUMP等。然后,根据找到的指令,您可以决定允许写入寄存器或添加寄存器或您选择在架构中包含的任何其他内容。

幸运的是,已经为您选择了机器指令二进制值的任意约定(如果您想要遵循它们)。这完全由指令集架构(ISA)定义的内容。例如MIPSHERA等。为了清楚起见,您在设计电路时创建的实际实现以及所谓的微架构

学习资源

文本

哈里斯和哈里斯的书是本科计算机体系结构课程最着名的文本之一。这是一个非常简单和有用的文本。一些随机学校免费提供PDF here全文。 (快下载!)我发现它非常有用。它通过基本电路,离散数学的主题,当你到第7章建立一个微处理器是一块蛋糕。读完那本书后,我花了大约3天才完成一个16位微处理器。 (当然,我有离散数学的背景,但这并不是非常重要。)

另一本非常有用且非常标准的书是Hennessy and Patterson本书,以PDF格式从一些随机学校获得。 (快速下载!)哈里斯和哈里斯的书是基于这本书的简化。这本书详细介绍了。

开源微处理器

那里有大量的开源微处理器。在构建我的第一个微处理器时,能够引用它们对我来说非常有帮助。使用Logisim文件的那些文件特别好用,因为你可以用图形方式查看它们并点击它们就像那样混乱。这里有一些我最喜欢的网站和特定的灯具:

4位:

16位:

Open Cores - 我真的没有这个网站。我申请了一个帐户,但他们还没有真正回来......不是一个大粉丝,但我想如果你有一个帐户,它一定很棒。

工具

Logisim

如前所述,Logisim是一个很好的资源。布局完全是图形化的,您可以通过选择导线轻松地在任何时间点看到发生了什么。它是在Java中,因此我非常确定它适用于您想要的任何机器。它也是图形计算机编程语言的一个有趣的历史视角。

模拟

在Logisim中,您可以模拟正在运行的实际软件。如果您有一个编译器可以将二进制文件编译为您要定位的ISA,那么您只需将二进制或十六进制文件加载到Logisim RAM中并运行该程序即可。 (如果你没有编译器,它仍然是可能的,并且是一个很好的练习来编写一个四行汇编程序并自己手动翻译。)模拟是迄今为止最酷和最令人满意的部分。整个过程! :D Logisim还提供CLI以便以编程方式执行此操作。

HDL

更现代的生成/设计微架构的形式是通过使用硬件描述语言(HDL)。最着名的例子包括Verilog和VHDL。这些通常(令人困惑!)模仿像Ada和C / C ++这样的顺序语言。然而,这是迄今为止优选的设计方法,因为更好地定义了模型/设计的验证。在我看来,理解文本表示比理解图形要容易得多。正如程序员可以很好地组织代码一样,硬件开发人员可能很难组织微架构的图形设计的图形布局。 (虽然这个论点当然可以应用于HDL。)通过文本方式比图形化更容易记录,并且通常使用HDL更加模块化地设计。

如果您对此有兴趣,那么有大量的本科硬件课程,开放课程和实验室工作,讨论和学习使用HDL来描述电路和微架构。你可以通过谷歌搜索找到这些。或者您也可以尝试通过下一步学习HDL - 将C / C ++代码转换为HDL的工具。如果您有兴趣,那么Icarus Verilog是Verilog的一个很好的开源编译器和模拟器。

模拟

使用像Icarus Verilog这样的工具,您还可以轻松地模拟从二进制文件运行的真实程序。您只需将微处理器包装在另一个Verilog脚本中,该脚本通过某个总线将文件或字符串加载到RAM中。小菜一碟! :d

HLS

近年来,高级综合(HLS)在市场上也获得了significant立足点。这是将C / C ++代码转换为实际电路。这非常令人难以置信,因为现有的C / C ++可以(但不总是)转换成硬件。

(我说并不总是因为并非所有的C / C ++代码都是可合成的。在电路中,比特流同时存在于所有地方。在软件中,我们认为代码是顺序的。这是如果你想设计硬件,可以考虑一种可怕的模式!)

但是你可能会猜到这种能力对于优化硬件上代码的某些方面(例如矩阵运算或数学)来说是不可思议的。但是,这与您相关,因为您可以使用HLS工具来查看点积(例如)的C实现如何被转换为HDL。我个人觉得这是一种很好的学习方式。

模拟

HLS仿真与模拟HDL一样简单,因为高级代码只需将转换为 HDL。然后你就可以完全按照我上面的解释来模拟和运行测试。

答案 1 :(得分:5)

尝试构建自己的简单CPU。它并不像看起来那么难: LOGISIM

答案 2 :(得分:5)

这是一个非常简单的6502指令集模拟器的示例框架(因为你不止一次提到6502)。它从一个简单的6502程序开始,这些是我要演示的唯一指令,但即使使用这样的程序,你也可以获得理解并立即获得看到某些工作的满足感。

   and #$00
   ora #$01
 top:
   rol
   bcc top
   and #$00

是的,我很清楚我没有正确启动模拟器。我假设这个二进制文件基于地址零,使用xa65汇编程序(apt-get install xa65)。组装后的二进制文件是:

hexdump -C a.o65 
00000000  29 00 09 01 2a 90 fd 29  00                       |)...*..).|
00000009

这是简单的,简化的指令,模拟器

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

FILE *fp;

#define MEMMASK 0xFFFF
unsigned char mem[MEMMASK+1];

unsigned short pc;
unsigned short dest;
unsigned char a;
unsigned char x;
unsigned char y;
unsigned char sr;
unsigned char sp;
unsigned char opcode;
unsigned char operand;

unsigned char temp;

int main ( void )
{
    memset(mem,0xFF,sizeof(mem)); //if we execute a 0xFF just exit

    fp=fopen("a.o65","rb");
    if(fp==NULL) return(1);
    fread(mem,1,sizeof(mem),fp);
    fclose(fp);

    //reset the cpu
    pc=0; //I know this is not right!
    a=0;
    x=0;
    y=0;
    sr=0;
    sp=0;
    //go
    while(1)
    {
        opcode=mem[pc];
        printf("\n0x%04X: 0x%02X\n",pc,opcode);
        pc++;
        if(opcode==0x29) //and
        {
            operand=mem[pc];
            printf("0x%04X: 0x%02X\n",pc,operand);
            pc++;
            printf("and #$%02X\n",operand);
            a&=operand;
            if(a==0) sr|=2; else sr&=(~2);
            sr&=0x7F; sr|=a&0x80;
            printf("a = $%02X sr = $%02X\n",a,sr);
            continue;
        }
        if(opcode==0x09) //ora
        {
            operand=mem[pc];
            printf("0x%04X: 0x%02X\n",pc,operand);
            pc++;
            printf("ora #$%02X\n",operand);
            a|=operand;
            if(a==0) sr|=2; else sr&=(~2);
            sr&=0x7F; sr|=a&0x80;
            printf("a = $%02X sr = $%02X\n",a,sr);
            continue;
        }
        if(opcode==0x2A) //rol
        {
            printf("rol\n");
            temp=a;
            a<<=1;
            a|=sr&0x01;
            sr&=(~0x01); if(temp&0x80) sr|=0x01;
            if(a==0) sr|=2; else sr&=(~2);
            sr&=0x7F; sr|=a&0x80;
            printf("a = $%02X sr = $%02X\n",a,sr);
            continue;
        }
        if(opcode==0x90) //bcc
        {
            operand=mem[pc];
            printf("0x%04X: 0x%02X\n",pc,operand);
            pc++;
            dest=operand;
            if(dest&0x80) dest|=0xFF00;
            dest+=pc;
            printf("bcc #$%04X\n",dest);
            if(sr&1)
            {
            }
            else
            {
                pc=dest;
            }
            continue;
        }
        printf("UNKNOWN OPCODE\n");
        break;
    }
    return(0);
}

和该简单程序的模拟器输出。

0x0000: 0x29
0x0001: 0x00
and #$00
a = $00 sr = $02

0x0002: 0x09
0x0003: 0x01
ora #$01
a = $01 sr = $00

0x0004: 0x2A
rol
a = $02 sr = $00

0x0005: 0x90
0x0006: 0xFD
bcc #$0004

0x0004: 0x2A
rol
a = $04 sr = $00

0x0005: 0x90
0x0006: 0xFD
bcc #$0004

0x0004: 0x2A
rol
a = $08 sr = $00

0x0005: 0x90
0x0006: 0xFD
bcc #$0004

0x0004: 0x2A
rol
a = $10 sr = $00

0x0005: 0x90
0x0006: 0xFD
bcc #$0004

0x0004: 0x2A
rol
a = $20 sr = $00

0x0005: 0x90
0x0006: 0xFD
bcc #$0004

0x0004: 0x2A
rol
a = $40 sr = $00

0x0005: 0x90
0x0006: 0xFD
bcc #$0004

0x0004: 0x2A
rol
a = $80 sr = $80

0x0005: 0x90
0x0006: 0xFD
bcc #$0004

0x0004: 0x2A
rol
a = $00 sr = $03

0x0005: 0x90
0x0006: 0xFD
bcc #$0004

0x0007: 0x29
0x0008: 0x00
and #$00
a = $00 sr = $03

0x0009: 0xFF
UNKNOWN OPCODE

完整的6502指令集是一个长周末最低价值的工作,如果你从头开始编写,因为你弄清楚你可能最终重启项目几次,非常自然。处理器的硬件(通常,不一定是6502)在概念上与模拟器中发生的情况完全不同。您必须获取指令,解码指令,获取操作数,执行并保存结果。就像在软件中执行此操作一样,在硬件中,您可以创建有趣的方法来使其更快或更小,或者无论您的目标是什么。

6502仍然是一个很大的项目,如果你实现整个事情,而不是像z80那么大,但像risc16这样的东西可能需要半个小时来理解和编写整个模拟器(然后另外半个小时来制作汇编程序)。 pic12,14或16比risc16更多的工作,但不是太多,可以很快地通过它,并且是一个教育体验,设计是多么简单。毫无疑问,pdp11和msp430在某种程度上是相关的,两者都有很好的记录(所有我提到的都有很好的记录)和很好/大部分是正交的,像解码一样的risc是与6502 / z80 / x86这样的cisc不同的体验。 (gp / gnu工具本身支持pdp11)。如果你可以围绕分支推迟插槽工作你的算法,那么Mips非常简单。

祝你好运,玩得开心......

答案 3 :(得分:4)

麻省理工学院将所有资源用于他们的Computation Structures类,在线6.004。它本质上是一个计算机体系结构类简介,它非常注重基于项目的学习。

OpenCourseWare版本为here。我特别专注于labs,特别是2,3,5和6,它们将你从构建ADDER到ALU一直带到一个非常简单的处理器(Beta)。

最新课程资料的链接是here

我记得当我把这个课程作为第一次“点击”时,在理解简单的逻辑门和电子元件如何转化为我用来模拟它的复杂机器方面。强烈推荐的资源。

答案 4 :(得分:3)

一个文本带你一步一步从大门到一台功能齐全的计算机以及更远的是计算系统的元素作者:Noam Nisan和Shimon Schocken 2005 ISBN 9780262640688.他们的网站位于{{ 3}}

答案 5 :(得分:0)

The Art of Assembly Language的前几章讨论了如何使用锁存器物理存储位,并讨论如何使用它们来组装寄存器以保存数据字节以及如何在左/右移位等指令中实现硬件。可能没有你想要的那么深入,但是当我读到这些东西时,它确实让我大开眼界。