Little bit of a confusing question. But Im really looking for learning some low level programming. Thing is, Dev boards like Arduino/Etc. really hide alot of whats going on.
Here is an example framework for a very simple 6502 instruction set simulator (since you mentioned 6502 more than once). it starts with a simple 6502 program, and these are the only instructions that I am going to demonstrate, but even with a program like this you can get an understanding and have some instant gratification of seeing something work.
and #$00
ora #$01
top:
rol
bcc top
and #$00
Yes, I am well aware that I am not booting the simulator properly. I am assuming this binary is based at address zero, using the xa65 assembler (apt-get install xa65). The binary after being assembled is:
hexdump -C a.o65
00000000 29 00 09 01 2a 90 fd 29 00 |)...*..).|
00000009
and this is the simple, reduced instruction, simulator
#include
#include
#include
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);
}
and the simulator output for that simple program.
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
the full 6502 instruction set is a long weekend minimums worth of work if you write it from scratch, as you figure stuff out you may end up restarting the project a few times, perfectly natural. The hardware (in general, not necessarily 6502) for a processor isnt all that much different in concept to what happens in a simulator. You have to get the instruction, decode the instruction, fetch the operands, execute, and save the results. Just like with doing this in software, in hardware you can create interesting ways to make it faster or smaller or whatever your goal might be.
6502 is still a big project if you implement the whole thing, not as big as z80, but things like risc16 is maybe half an hour to understand and write the whole simulator (then another half hour to make an assembler). the pic12, 14, or 16 is more work than risc16 but not too much more, can bang through it quickly and is an educational experience with how simple the design is. the pdp11 and msp430 are no doubt related somehow, both well documented (all of the ones I mention are well documented) and nicely/mostly orthogonal and the risc like decoding is a different experience than the cisc like 6502/z80/x86. (pdp11 is supported natively by gcc/gnu tools). Mips is super simple if you can work your algorithm around the branch defer slot.
good luck, have fun...