News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

6502 Emulator with HLA

Started by indiocolifa, September 18, 2005, 08:50:54 AM

Previous topic - Next topic

indiocolifa

I'm starting a 6502 Emulator in HLA, and I've defined the following type which tries to model the main CPU registers :


TYPE cpuRegs: RECORD [4]
A : byte; // accumulator
X : byte; // x reg
Y : byte; // y reg
_SP : word; // stack pointer
PC : word; // program counter
FLAGS: byte; // CPU flags
ENDRECORD;


Well, I coded the STA instruction in immediate addressing mode.
The operation of STA is simply to store an immediate 8-bit value into the A register (accumulator).
I've done this code:



procedure LDA__imm (var regs: cpuRegs ; var mem: sysmem ; var clk : sysclk); @nodisplay;
begin LDA__imm;

push       (eax);
push       (ebx);
push       (edx);

lea        (eax, regs);
lea        (edx, mem);
movzx      ((type cpuRegs [eax]).PC, ebx);                 // get operand for instruction
inc        (ebx);                                          // at PC+1
movzx      ((type byte mem[ebx]), edx);                    // in reg EDX
mov        (edx, (type dword (type cpuRegs [eax]).A));     // store into accumulator

inc        ( (type cpuRegs[eax]).PC );                     // PC=PC+1
lea        (eax, clk);
add        ( 2, (type sysclk [eax]));                      // +2 cycles               

pop        (edx);
pop        (ebx);
pop        (eax);


end LDA__imm;


Note that I excluded from above the flag set/clear behavior of the instruction.

My question is how to avoid those type-castings. Should I map the 8-bit CPU regs to, for example, AH/AL?

If I do

MOV ((type cpuRegs[eax].PC, dx)

Gives type mismatch... Why? PC = word -> dx = word.

Give me recommendations on doing this.

Thank you very much. :U

Sevag.K

I'm not getting a type mismatch error on mine.

There is a potential memory access violation with your code.  You are using "var" for your procedure arguments which automatically loads the effective addresses into the variables.

If you use:

lea (eax, regs);

You are effectively loading the address of the address which is probably not what you want.  Use 'mov' instead of 'lea'


Type casting can't be avoided unless you want to write in the offsets yourself:

mov (regs, eax);
mov ([eax+6], bx);

You can ease this process by adding consts:

const PC := 6;

...

mov ([eax+PC], bx);

Another way to ease the process (while still using the records) is to use a text const:

const r :text := "(type cpuRegs[eax])";
...

mov ( r.PC, bx);

===============

mov        (edx, (type dword (type cpuRegs [eax]).A));     // store into accumulator

I don't know how the 6502 works, but do you realize that this will zero out X, Y and the first byte of _SP ? (if the proper address is given of course, in the case of the program you have, it will overwrite some unknown memory.

===============

On another note, Roger Taylor wrote a 6809/6309 cross-assembler IDE using HLA.  He might be able to give you some pointers.  See his web-site here:
http://www.coco3.com/index.php

Here is a short test program to give you a visual of what's going on when useing 'var'

program tt;

#include ("stdlib.hhf")

static
ttt :dword := $a;
endstatic;

procedure one ( var two:dword );

begin one;
mov (two, eax);
stdout.put ("Address of ttt:", eax, nl);
stdout.put("Value of ttt:", (type dword [eax]),nl,nl);

lea (eax, two);
stdout.put ("Address of pointer (two):", eax, nl);
stdout.put ("Some unknown area (might cause mem access violation): ", (type dword [eax]),nl);
end one;



begin tt;

mov (&ttt, eax);
stdout.put ("Address of ttt:", eax, nl);

one (ttt);

end tt;


indiocolifa

Before let my say that HIDE is great.. keep up the good work!

All right, I'm trying to rewrite that mess  :wink
Well since the 6502 CPU is an 8-bit micro and I've created the CPUREGS record to hold 8-bit values and a 16-bit PC / SP, I will try to map the 8-bit 6502 registers to x86 8/16-bit regs (AX/AH/AL,etc).

The instruction I'm trying to implement (STA) simply does:

- Fetch byte operand from next memory position
- Load immediate byte into accumulator.
- Increment PC := PC + 2
- Increment clock cycles by 2.

I'm doing...


procedure LDA__imm (var regs: cpuRegs ; var mem: sysmem ; var clk : sysclk); @nodisplay;
const
    REGS_PTR : text := "(type cpuRegs[ebx])";
begin LDA__imm;
push       (ebx);
mov        (regs, ebx);                                    // addr of regs -> ebx
mov       (REGS_PTR.PC, cx);           // get program counter in cx
end LDA__imm;


Now I'm trying to get the byte at mem[cx+1] (the next byte in program counter), but I'm failing!





indiocolifa

Here's my (hopefully) working code (with flags register modification):



const
    REGS_PTR     : text := "(type cpuRegs[ebx])";
    NEXTMEM_PTR  : text := "(type sysmem mem[ecx+1])";
    SYSCLK_PTR   : text := "(type sysclk[ebx])";

procedure LDA__imm (var regs: cpuRegs ; var mem: sysmem ; var clk : sysclk); @nodisplay; 
begin LDA__imm;

    push       (eax);
push       (ebx);
push       (ecx);

xor        (ecx, ecx);
mov        (regs, ebx);                // addr of regs -> ebx
mov        (REGS_PTR.PC, cx);          // get program counter in cx
mov        (NEXTMEM_PTR, al);          // get next operand in al
mov        (al, REGS_PTR.A);           // store operand into accumulator
add        (2, REGS_PTR.PC);        // PC=PC+2
mov        (clk, ebx);                 // addr of clk cycles count
add        (2, SYSCLK_PTR);            // +2 clk cycles

IF (al==0) THEN
   or          (2, REGS_PTR.FLAGS);    //  set flag Z=1
ELSE
   and         ($FD, REGS_PTR.FLAGS);  //  set Z=0
ENDIF;

test       ($80, al);                  //  flag N=1?
IF (@z) THEN
   or          ($80, REGS_PTR.FLAGS);  //  set N=1
ELSE
   and         ($7F, REGS_PTR.FLAGS);  //  set N=0
ENDIF;    

pop        (ecx);
pop        (ebx);
pop        (eax);

end LDA__imm;



What do you think of it?

Sevag.K

I'm assuming 'sysmem' is an array of type 'byte'.  If it's memory allocated on the heap, the type casting is the wrong approach.  You may have a problem there.  I suspect that "(type sysmem mem[ecx+1])"; will not give you the correct memory address, though I could be wrong not seeing the rest of the program.  For example, if "mem" is already a memory pointer, then by using 'pass by referrence,'  you're getting the pointer of a pointer.  In such a case, drop the 'var' and just use pass by value.

The way I figure it, this LDA_imm instruction will load the next byte of memory into the 'A' register and move the program counter beyond this byte.

Thanks for the compliment on HIDE.  Looks like you have an interesting project going here yourself and I certainly woudl be interested in following the progress.

If your still having problems, I wrote a quick program that loads some values into memory and fetches them with a shortened version of your LDA


program ttest;
#include ("stdlib.hhf")

TYPE cpuRegs: RECORD [4]
A : byte; // accumulator
X : byte; // x reg
Y : byte; // y reg
_SP : word; // stack pointer
PC : word; // program counter
FLAGS: byte; // CPU flags
ENDRECORD;

sysmem :dword;

endtype;


static
memory :pointer to sysmem;
registers :pointer to cpuRegs;
endstatic;

// notice 'mem' is pass by value instead of pass by reference
procedure LDA__imm (var regs: cpuRegs ; mem: sysmem); @nodisplay;
const
    REGS_PTR     : text := "(type cpuRegs[ebx])";

begin LDA__imm;

push       (eax);
push       (ebx);
push (edx);
push       (ecx);

xor        (ecx, ecx);
mov (mem, edx);
mov        (regs, ebx);                // addr of regs -> ebx
mov        (REGS_PTR.PC, cx);          // get program counter in cx
mov ([edx+ecx+1], al); // fetch next byte from memory

mov        (al, REGS_PTR.A);           // store operand into accumulator
add        (2, REGS_PTR.PC);        // PC=PC+2

pop        (ecx);
pop (edx);
pop        (ebx);
pop        (eax);

end LDA__imm;



begin ttest;

mov (mem.alloc (100), memory);

// load some bytes into memory (0 .. 9)
mov (memory, ebx);
for (xor (eax, eax); eax < 10; inc (eax) ) do
mov (al, [ebx+eax]);
endfor;

mov ( mem.alloc( @size(cpuRegs)), registers);
mov (registers, ebx);

// fetch every other byte using LDA
for (xor (eax, eax); eax < 5; inc (eax) ) do
LDA__imm (registers, memory);
stdout.put ("fetched byte, in register 'A' :", (type cpuRegs[ebx]).A,nl);
endfor;

end ttest;



indiocolifa

sysmem is not a memory address since it's a 64K static array... but maybe it's better to dynamically allocate the RAM at program startup.

Thakn you very much for your response.

gabor

Hello Indiocolifa!

I am not familiar with HLA :(
I have 2 things to add
1. What is your goal with this 6502 emu? Do you intend to create a c64 emu? If yes, may I join the work? Send me a PM!

2. This is just a small remark: the instruction to load a value into the acc is LDA, STA work the other way: to write the value in acc into the memory...

Greets, Gábor

indiocolifa

1. My goal is to create an usable 6502 core (my intention is to later add the rare 65CE02 opcodes, or why not WDC65816 emulation)
2. Maybe later we can start a C64 emulation project of course!

indiocolifa

And yes, LDA loads accumulator and STA stores accumulator into memory.

indiocolifa

This is the current, very early, progress of my 6502 project in HLA. ::)

Read the manual.txt file for the very few implemented commands (i'm currently implementing the 6502 opcodes, be patient!)





[attachment deleted by admin]

Sevag.K

Looking good so far.

Getting unhandled exception when entering data 'e' command and the data is not hex.