12 embedded assembly 6502 emulation computer-architecture
有点令人困惑的问题.但我真的在寻找学习一些低级编程.事情是,开发板像Arduino/Etc.真的隐藏了很多正在发生的事情.
我花了一些时间学习计算机体系结构,逻辑/门/顺序逻辑/等等.(我甚至去了解与半导体和电子相关的物理学,只是为了知道到底发生了什么,就像以及如何使用CMOS晶体管等制造盖茨.
但那就是它结束的地方......我希望能够理解一个指令(如Hex /或汇编/等等代码)是如何通过一台简单的计算机(我用过的很多书)直接从盖茨到计算机....没有真正的介于两者之间).甚至是简单的东西.....将值存储在寄存器或存储器位置(并且可能打印到像素?或某些东西).
我认为最有趣的事情可能是最终编写模拟器.我有使用高级语言的经验,但我听说像6502这样的东西可能是一个好的开始,因为你使用了很多汇编,并且指令集不是太大.
有谁知道任何可能有帮助的资源/想法/书籍?我已经阅读了"计算系统的元素",虽然......这是一本很好的书,我真的不觉得它真的发生了什么,看到它发生了.这可能更像是一个Electronics.stackexchange问题,如果是这样,我道歉.
你真的有很多选择.我将总结我对如何翻译指令的看法,但我还将提供一些我在开始时的选项.
首先,从二进制输入的角度考虑是最容易的.假设您有一个16位微处理器.(即,指令以16位二进制位编码.)考虑将数字放入寄存器的汇编操作SET.例如:
SET(R1, 12) // Stores 12 into register 1
Run Code Online (Sandbox Code Playgroud)
让我们任意(因为即使在任何标准架构中,选择都是任意的)选择将SET指令转换为以下16位二进制值I:
0001 0001 0000 1100
Run Code Online (Sandbox Code Playgroud)
基本上,我刚刚组成了一个会议.但这是我如何分解它.我选择让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.
Run Code Online (Sandbox Code Playgroud)
如您所见,微处理器中的其他所有内容都变得非常简单.假设您的商店在RAM中存储了4行指令.你有一个附在时钟上的计数器.计数器通过RAM中的行计数.当时钟"滴答"时,一条新指令从ram中出来.(这是下一条指令来自RAM - 尽管在插入JUMP语句时这可能有点武断.)RAM的输出通过多个位选择器.你选择我的位[15:12],并将其发送到控制单元(CLU),它会告诉你哪个指令你想传达.即,SET,JUMP等.然后,根据找到的指令,您可以决定允许写入寄存器或添加寄存器或您选择在架构中包含的任何其他内容.
谢天谢地,已经为您选择了机器指令二进制值的任意约定(如果您想要遵循它们).这正是指令集架构(ISA)定义的内容.例如MIPS,HERA等.为了清楚起见,您在设计电路时创建的实际实现以及所谓的微架构.
哈里斯和哈里斯的书是本科计算机体系结构课程最着名的文本之一.这是一个非常简单和有用的文本.整个事情是一个PDF可以在这里免费用一些随机的学校.(快下载!)我发现它非常有用.它通过基本电路,离散数学的主题,当你到第7章建立一个微处理器是一块蛋糕.读完那本书后,我花了大约3天才完成一个16位微处理器.(当然,我有离散数学的背景,但这不是非常重要.)
另一本非常有用且非常标准的书是轩尼诗和帕特森的书,也可以从一些随机学校以PDF格式提供.(快速下载!)哈里斯和哈里斯的书是基于这本书的简化.本书详细介绍了这本书.
那里有大量的开源微处理器.在构建我的第一个微处理器时,能够引用它们对我来说非常有帮助.使用Logisim文件的那些文件特别好用,因为你可以用图形方式查看它们并点击它们就像那样混乱.这里有一些我最喜欢的网站和特定的灯具:
4位:
16位:
开放核心 - 我真的没有这个网站.我申请了一个帐户,但他们还没有真正回来......不是一个大粉丝,但我想如果你有一个帐户,它一定很棒.
如前所述,Logisim是一个很好的资源.布局完全是图形化的,您可以通过选择导线轻松地在任何时间点看到发生了什么.它是在Java中,所以我很确定它适用于你想要它的任何机器.它也是图形计算机编程语言的一个有趣的历史视角.
在Logisim中,您可以模拟正在运行的实际软件.如果您有一个编译器可以将二进制文件编译为您要定位的ISA,那么您只需将二进制或十六进制文件加载到Logisim RAM中并运行该程序即可.(如果你没有编译器,那么编写一个四行汇编程序并自己亲自翻译它仍然是可能的,也是一个很好的练习.)模拟是整个过程中最酷和最令人满意的部分!:D Logisim还提供了一个CLI,用于以编程方式执行此操作.
生成/设计微架构的更现代形式是通过使用硬件描述语言(HDL).最着名的例子包括Verilog和VHDL.这些通常(令人困惑!)模仿像Ada和C/C++这样的顺序语言.然而,这是迄今为止优选的设计方法,因为更好地定义了模型/设计的验证.在我看来,推理文本表示要比以图形方式检查要容易得多.正如程序员可以很好地组织代码一样,硬件开发人员可能很难组织微架构的图形设计的图形布局.(虽然这个论点当然可以应用于HDL.)通过文本而不是图形化更容易记录,并且通常使用HDL来更加模块化地设计.
如果您有兴趣了解这一点,那么有大量的本科硬件课程,开放课程和实验室工作,讨论和学习如何使用HDL来描述电路和微架构.你可以通过谷歌搜索找到这些.或者您也可以尝试通过下一步学习HDL - 将C/C++代码转换为HDL的工具.如果您有兴趣,那么Icarus Verilog是Verilog的一个很好的开源编译器和模拟器.
使用像Icarus Verilog这样的工具,您还可以轻松地模拟从二进制文件运行的真实程序.您只需将微处理器包装在另一个Verilog脚本中,该脚本通过某个总线将文件或字符串加载到RAM中.小菜一碟!:d
近年来,高级综合(HLS)也在市场上获得了重要的立足点.这是将C/C++代码转换为实际电路.这非常令人难以置信,因为现有的C/C++可以(但不总是)转换为硬件.
(我说并不总是因为不是所有的C/C++代码都是可综合的.在一个电路中,比特流同时存在于各个地方.在软件中,我们认为代码是连续的.如果你正在尝试,这是一种可怕的模式.设计硬件!!)
但是你可能会猜到这种能力对于优化硬件上代码的某些方面是不可思议的,例如矩阵运算或数学.但是,这与您相关,因为您可以使用HLS工具来查看点积(例如)的C实现如何被转换为HDL.我个人觉得这是一个很好的学习方式.
HLS仿真与模拟HDL一样简单,因为高级代码只是简单地转换为 HDL.然后你可以完全按照我上面的解释模拟和运行测试.
这是一个非常简单的6502指令集模拟器的示例框架(因为您不止一次提到6502).它从一个简单的6502程序开始,这些是我要演示的唯一指令,但即使使用这样的程序,您也可以获得理解并立即获得看到某些工作的满足感.
and #$00
ora #$01
top:
rol
bcc top
and #$00
Run Code Online (Sandbox Code Playgroud)
是的,我很清楚我没有正确启动模拟器.我假设这个二进制文件基于地址零,使用xa65汇编程序(apt-get install xa65).组装后的二进制文件是:
hexdump -C a.o65
00000000 29 00 09 01 2a 90 fd 29 00 |)...*..).|
00000009
Run Code Online (Sandbox Code Playgroud)
这是简单的,简化的指令,模拟器
#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);
}
Run Code Online (Sandbox Code Playgroud)
和那个简单程序的模拟器输出.
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
Run Code Online (Sandbox Code Playgroud)
完整的6502指令集是一个长周末最小值的工作,如果你从头开始编写,因为你想到的东西你可能最终重新启动项目几次,非常自然.处理器的硬件(通常,不一定是6502)在概念上与模拟器中发生的情况完全不同.您必须获取指令,解码指令,获取操作数,执行并保存结果.就像在软件中执行此操作一样,在硬件中,您可以创建有趣的方法来使其更快或更小,或者无论您的目标是什么.
6502仍然是一个很大的项目,如果你实现整个事情,而不是像z80那么大,但像risc16这样的东西可能需要半个小时来理解和编写整个模拟器(然后再用半个小时制作一个汇编程序).pic12,14或16比risc16更多的工作,但不是太多,可以很快地通过它,并且是一个教育体验,设计是多么简单.毫无疑问,pdp11和msp430在某种程度上是相关的,两者都有很好的记录(所有我提到的都有很好的记录)和很好/大部分是正交的,像解码一样的risc是与6502/z80/x86这样的cisc不同的体验.(gp/gnu工具本身支持pdp11).如果你可以围绕分支延迟槽工作你的算法,Mips是非常简单的.
祝你好运,玩得开心......