6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Fri Sep 20, 2024 7:33 pm

All times are UTC




Post new topic Reply to topic  [ 10 posts ] 
Author Message
PostPosted: Sun Feb 24, 2008 5:08 am 
Offline

Joined: Tue Dec 30, 2003 10:35 am
Posts: 42
I write lots of test programs that are run on hardware and emulators to be sure they both have the same behavior. People often step through them with a debugger in emulators, so I'd like them to be relatively easy to disassemble. I have a few routines that take constant parameters and preserve all registers (so they can be inserted anywhere without worrying about stomping on anything). Putting the value/address into registers means I have to save and restore them, which becomes too bloated, especially since this is for the NMOS 6502 which lacks PHX, PHY, etc. As an example, I'll use a print_string routine that takes a zero-terminates ASCII string. The following are the various approaches I've come up with. I'm only focusing on the caller, since that's what matters most.

Code:
.zeropage
addr: .res 2
.rodata
test: .byte "Test",0
.code

; The most straight-forward way, but it's bloated
pha
tya
pha
lda #<test
ldy #>test
jsr print_string
pla
tay
pla

; More compact and still very clean. I might go with this.
pha
lda #<test
sta addr
lda #>test
jsr print_string
pla

; Will confuse a disassembler since it will interpret the string
; as instructions and could easily "munch" the next instruction.
; The return addr is used to find the string data, then execution
; resumes just after the zero terminator.
jsr print_string
.byte "Test",0

; Also involves return address examination, but doesn't confuse
; disassembler/debugger. Still more complex to implement.
; The return addr is used to find the address inside the BIT
; instruction. The point of using BIT is that it only modifies
; flags, and we don't have to adjust the return address either,
; just read it.
jsr print_string
bit test

; Simpler to implement, since nested return addr would be popped off
; and never returned to. Main problem is then that each string
; printed turns into a JSR to a different address in the main code.
jsr print_test
...

print_test:
jsr print_string
.byte "Test",0

The details are of course encapsulated in a macro, so the user code just contains the following:

print_string "Test"

Any other ideas for making it play well with a disassembler?


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Sun Feb 24, 2008 9:06 pm 
Offline

Joined: Sat Jan 04, 2003 10:03 pm
Posts: 1706
Quote:
I write lots of test programs that are run on hardware and emulators to be sure they both have the same behavior. People often step through them with a debugger in emulators, so I'd like them to be relatively easy to disassemble.


This is why I always choose to rely upon automated testing, instead of manual testing, and in particular using test-driven development practices. Manual testing is laborious, error-prone, and a gigantic time-sink in terms of productivity.

Quote:
Any other ideas for making it play well with a disassembler?


Why not load the X register with an index into a table of message pointers, then have your print_string routine index the table to grab the appropriate address on its own? That permits up to 256 messages to be stored per print_string entry-point.

Code:
.proc print_string_proc
  pha
  tya
  pha

  lda stringTableL,x
  sta ptrL
  lda stringTableH,x
  sta ptrH

  ...etc...

  pla
  tay
  pla
  rts
.endproc


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Feb 25, 2008 1:41 am 
Offline

Joined: Tue Dec 30, 2003 10:35 am
Posts: 42
Quote:
This is why I always choose to rely upon automated testing, instead of manual testing, and in particular using test-driven development practices. Manual testing is laborious, error-prone, and a gigantic time-sink in terms of productivity.

Maybe I was unclear. OK, we have a piece of hardware that uses a 6502 processor. Someone wants to duplicate its behavior in a simulator. I write a test program that runs on the hardware and exercises some aspect of it, and verifies that it responds appropriately. This test program passes on hardware. Then the author runs it on his simulator. If it passes, great, but if it fails, the author might want to step through the simulated 6502 to find where his simulator is messing up. I'm not seeing how this could be automated.
Quote:
Why not load the X register with an index into a table of message pointers, then have your print_string routine index the table to grab the appropriate address on its own? That permits up to 256 messages to be stored per print_string entry-point.

Sure; got a macro to automatically assign message indicies and generate the message table? It's got to be really convenient to use.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Tue Feb 26, 2008 5:19 am 
Offline

Joined: Sat Jan 04, 2003 10:03 pm
Posts: 1706
blargg wrote:
Maybe I was unclear.


On the contrary -- you were crystal.

Quote:
If it passes, great, but if it fails, the author might want to step through the simulated 6502 to find where his simulator is messing up. I'm not seeing how this could be automated.


I'm not trying to be offensive when I say this, but rather honest. Not seeing how it could be automated is not the same as affirming that it cannot be automated. To believe so is just inexperience with automated testing procedures. There are two methods of testing at your disposal: internal and external. Internal testing would rely on the emulated 65816/6502 itself (assuming it is "correct enough" to do so), while External testing relies on instrumenting the simulator itself.

Internal testing depends on some amount of sweat-equity. Basically, you need a certain minimum set of opcodes to implement a unit test framework -- those opcodes need to be verified manually. However, such a framework will positively not rely on ALL of the CPU's instructions and addressing modes. Therefore, you can still rely on the unit testing framework you've written to exercise those instructions.

Once the CPU is verified, then you can use that to do some tests on well-known I/O peripherals too. Indeed, this is what most BIOS ROMs do during their "self-test." RAM size determination, keyboard I/O check, etc. All that isn't really for the user's benefit. The user couldn't really care less about the adequacy of RAM from boot to boot -- statistics says if it booted before, it'll boot again. Those tests are really for the BIOS and manufacturer's benefit -- if a peripheral fails to respond, it'll spew an error message, or beep a certain number of times, etc. This allows a computer technician, not a user!, to determine the fault, and replace motherboards appropriately.

External testing relies on instrumenting the simulator or emulator itself. This works by thorough modularization of the simulator's code. For example, my Kestrel emulator, written as a spike and not even fully tested in an automated fashion, is built modularly to facilitate easy testing. Address decoding, MGIA, keyboard/mouse, SerBus, and even the CPU core itself are all separate modules, all fully re-usable in other projects, with well-defined interfaces that are easy to test. Indeed, the SerBus implementation and the disk storage system were the first two components that I rigorously unit-tested. You can find the source to these in the Kestrel emulator's source package on my website. The unit tests exercise SerBus down to individual register-level transfers, completely without the aid of the emulated CPU.

I don't want to make myself out like I'm beating my chest. That's not my intent. I'm just saying, "yes," it is possible to automate the testing and verification of hardware against a software model (heck, that's what I did at Hifn as post-silicon verification technician!). For those who are interested in techniques on how to do this, there are volumes of websites on test-driven development, behavior-driven development, mock-object testing, etc. I'm not going to repeat them here.

Quote:
Sure; got a macro to automatically assign message indicies and generate the message table? It's got to be really convenient to use.


Me? No. Again, failure for you to think of how this is done does not mean it cannot be done.

Here's an idea, using a completely hypothetical assembly language syntax, based on my recollection of a 68000 assembler I used on the Amiga called A68K all the time.

Code:
Print   MACRO   Msg
        SECTION RODATA
L_Msg:  DC.B    Msg, 0

        SECTION MsgTableLoSection
idx     SET     (*-MsgTableLoOrigin)
        DC.B    <L_Msg

        SECTION MsgTableHiSection
        DC.B    >L_Msg

        SECTION CODE
        LDX     #idx
        JSR     printIndexedMessage
        ENDM


If your assembler provides a feature-set analogous to the above (the critical point here is that SET functions like EQU, but allows symbol re-definition), then it be structurally similar to the above. This even works across multiple compilation/assembly units -- the linker would coalesce all like-named sections appropriately, and take care of any load-time fixups.

At least, if it were a good system. :) I'm pretty sure ca65 offers a macro processor powerful enough for this, but I've never used it, so my memory could be incorrect. Fachat's assembler (xa65 IIRC) might also be powerful enough to support this kind of construct.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Tue Feb 26, 2008 9:15 am 
Offline

Joined: Tue Dec 30, 2003 10:35 am
Posts: 42
Quote:
I'm just saying, "yes," it is possible to automate the testing and verification of hardware against a software model.

That's exactly what I'm doing already. User runs automated test. User's simulator fails the test with the result "Reading from $2002 should suppress NMI if read just when it's set". User wants to figure out exactly why it's failing, so he examines his code but doesn't find an obvious cause. He starts integrated 6502 debugger and steps through the test, to see if he can get some idea of what's happening. He comes to a JSR PRINT_STR followed by an ASCII string, which looks like junk instructions, which makes the task more difficult for him. But I give up in trying to get this point across, since it's just not going to make it past the tangential issue you've decided to use this thread for. In that spirit, I'll use it as the context for why people like you drive people like me away:

This topic was about disassembler-friendly coding, not about automated testing of programs. I mentioned the context only so it would make a bit more sense. It wasn't an invitation for advice on the subject of testing. If it were, I would have given more information so that useful advice could be given, and titled the thread accordingly. As it is, you made unwarranted assumptions and refused to correct them. I know much more about the project, yet you ignore my attempts to say "please stick to the topic and stop offering unwanted, inapplicable advice". You continued to hammer this tangential issue in a condescending way. Somehow my feedback isn't able to alter your view of my situation; your paradigm seems to convert it into more confirmation that your view is correct and that I am in need of enlightenment from you. You may consider me to be too sensitive, but sensitivity and being mentally open are essential to finding novel solutions to problems, and this being a technical forum for solving problems, I come here with an open mind. There is just no excuse for the mean approach you take, and I'm not going to try to accommodate it. I don't know why it's tolerated. People like you make me be on constant guard for having my words twisted to fit your view, and I absolutely hate it. I doubt I'm alone. You turn things into battles, respond to people in a predatory way, and in general kill the atmosphere of exploration and open sharing. And that's all.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 27, 2008 4:58 am 
Offline
User avatar

Joined: Thu Mar 11, 2004 7:42 am
Posts: 362
A couple of variations on the theme...

Code:
; Just like PRIMM, except (a) it uses return address + 4 instead of
; return address + 1, and (b) it doesn't (need to) modify the return
; address on the stack.  Since the data is preceded by a JMP it doesn't
; look like its falling through to garbage.
;
; Since neither JSR or JMP affects any flags or registers, preservation of
; registers and/or flags can be done inside print_string rather than by
; the caller.
;
   JSR print_string
   JMP L1
   .byte "string",0
L1


Variation #2. I would recommend using INX(s) with $100,X rather than $100+n,X (depending on when you do a TSX) inside print_string so that it works no matter what the value of the stack pointer is.

Code:
; Uses PHA and PLA instead of STA data.  print_string reads the lo byte
; from the stack, but doesn't alter the stack.
;
; The main advantage is that there is no need to worry about any memory
; conflicts with the "data" variable, or the fact that its address may
; different from program to program or even from build to build (easier
; to follow when looking at a disassembly without labels).
;
PHP              ; save flags
PHA              ; save A
LDA #<string
PHA              ; pass the lo byte on the stack
LDA #>string     ; pass the hi byte in A
JSR print_string
PLA              ; discard the lo byte
PLA              ; restore A
PLP              ; restore flags


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 27, 2008 5:24 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8510
Location: Southern California
Quote:
Code:
   JSR   print_string
   JMP   L1
   .byte "string",0
L1

If you replace JMP L1 with BRA L1, the parameter becomes the string length (assuming you don't need more than 127) and you can save two bytes.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 27, 2008 6:25 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 9:02 pm
Posts: 1738
Location: Sacramento, CA
Here's a thought....

Place 3 NOP instructions after your string text.

The worst case is the last byte of text is interpreted as an instruction that uses the $00 and possibly one NOP as its operands. The Next two NOP's ensure that the disassembler can lock onto the code after the NOP's. It would not "Munch" any valid instructions. The PRINT_IMM would not need modification as it would just execute the NOP's and continue.

Its not space saving, efficient, or brilliant, but it would work, wouldn't it?

Daryl


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Feb 27, 2008 11:49 pm 
Offline
User avatar

Joined: Thu Mar 11, 2004 7:42 am
Posts: 362
GARTHWILSON wrote:
If you replace JMP L1 with BRA L1, the parameter becomes the string length (assuming you don't need more than 127) and you can save two bytes.


According to the original post, it's using an NMOS 6502. Overwriting V usually won't cause any issues, so if print_string used CLV before its RTS, BVC could be used. The one thing that's nice about JMP is that it represents a consistent format for any inline data, regardless of length (the original post alluded to other routines).


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Thu Feb 28, 2008 4:01 pm 
Offline

Joined: Sat Jan 04, 2003 10:03 pm
Posts: 1706
Response to blargg's rude and presumptuous response to me removed, and taken off-line.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 10 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 13 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: