68000 programming.

edited July 2007 in Chit chat
Anyone here know anything about this?
My question is: how does the programmer know how long hisinstructions are? Do determine the operands to branch commands etc...
Post edited by wilsonsamm on

Comments

  • edited July 2007
    Programming 68000 is much the same as any other language, in that usually you use a compiler to calculate all the operands & jumps for you. If you really want to do it by hand then I'm sure you can download the instruction set from somewhere online which should also include each instruction's length in bytes.
  • edited July 2007
    Since the 68K is the native processor in the Amiga, try here:

    http://eab.abime.net/

    It's the Amiga eqivalent of WoSF.
    Oh bugger!<br>
  • edited July 2007
    wilsonsamm wrote: »
    My question is: how does the programmer know how long hisinstructions are? Do determine the operands to branch commands etc...

    The command size will vary depending on number and type of arguments, much like other CPUs. An assembler will sort it out for you if you label the code you want to jump to. I'd strongly recommend against trying to code it by hand in hex...

    I frequently use 680x0 asm and have the CPU manuals, so if there's something specific you wanted to know or there's a short piece of code you needed to create and you don't have an assembler, let me know and I'll try to help.
  • edited July 2007
    aowen wrote: »
    You're not writing a QL emulator for the Spectrum are you?
    Please make it compatible with the Spectrum SE...
    I wanna tell you a story 'bout a woman I know...
  • edited July 2007
    aowen wrote: »
    You're not writing a QL emulator for the Spectrum are you?
    oh, no. Just th?t "great i'm on my holidays. let's do something wacky and learn 68k assy". I'm trying to make a program that will run pdp-8 code. I know it's not a project one's advised to learn assembler with, but I'm always too ambitious and never finish things I start. Apart from food.
    But can someone tell me what's wrong with these instructions? I keep getting an "invalid adressing mode" error with them...
    andi.w $00FF,a1
    bclr $8,a1
    ori.w $FF80,a2
  • edited July 2007
    wilsonsamm wrote: »
    But can someone tell me what's wrong with these instructions? I keep getting an "invalid adressing mode" error with them...
    andi.w $00FF,a1
    bclr $8,a1
    ori.w $FF80,a2

    You can't do logical operations on address registers. So and, or, bit ops, etc are not allowed. Do them on data registers instead. You can move to and from address registers, as well as add and subtract, with some limitations, but condition codes are (usually) not affected when an address register is the destination.

    Also, for immediate (constant) numbers, prefix the argument with "#". A number without the # means an absolute memory address location.

    i.e:

    andi.w #$00ff,d0 ;AND $ff with d0.

    and.w $00ff,d0 ;AND contents of location $ff with d0.

    (Also, most assemblers will convert an "and" into and "andi" as required.)
  • edited July 2007
    wilsonsamm wrote: »
    oh, no. Just th?t "great i'm on my holidays. let's do something wacky and learn 68k assy". I'm trying to make a program that will run pdp-8 code. I know it's not a project one's advised to learn assembler with, but I'm always too ambitious and never finish things I start. Apart from food.
    But can someone tell me what's wrong with these instructions? I keep getting an "invalid adressing mode" error with them...
    andi.w $00FF,a1
    bclr $8,a1
    ori.w $FF80,a2
    I suspect it's because you're directly accessing an Address register.
    IIRC indirect access and direct (and indirect) access to Data registers only are allowed.
    I wanna tell you a story 'bout a woman I know...
  • edited July 2007
    Laser wrote: »
    A number without the # means an absolute memory address location.
    the contents of it?

    I'm finding it a little bothersome that there are limitations like what register you can use with what opcode. And people say it's orthogonal? pffft. not like the pdp-11 where you can do things like
    add 3,r6 #adding 3 to the stack pointer, like pushing three zeroes, or putting back whatever was there before
    or
    push (r7) #push current opcode onto stack (r7 is the program counter) using indirect adressing

    but still there's some amazing things being done with the 68k. maybe someday I will be able to harness this power.
  • edited July 2007
    wilsonsamm wrote: »
    I'm finding it a little bothersome that there are limitations like what register you can use with what opcode. And people say it's orthogonal? pffft.

    Yeah, I think orthogonal might be stretching the truth for 68K. :-)

    Like most CISC processors there are groups of registers that are optimised to perform certain functions. It certainly isn't as free-form as something like an ARM. On the other hand, it is fairly free (compared to most RISC cores) about whether the data is in memory or a register, so you "save" register use like that. (e.g. memory-to-memory transfers do not require an intermediate register.)
    not like the pdp-11 where you can do things like
    add 3,r6 #adding 3 to the stack pointer, like pushing three zeroes, or putting back whatever was there before
    or
    push (r7) #push current opcode onto stack (r7 is the program counter) using indirect adressing

    Well, if it makes you any happier, a7 is the stack pointer and you can do what you like with it in the same way as any other address register, although you'd rarely want to as it ruins the stacking.

    The 68K equivalents of push and pop are:

    push: move xxx,-(a7)
    pop: move (a7)+,xxx

    Check out movem for multiple registers:

    movem.l d0-d4/a3,-(a7) ;push d0,d1,d2,d3,d4,a3

    You can also "pea" - push effective address:

    pea blob ;push the address "blob" onto the stack.
    pea -$14(a0) ;push a0-$14 onto the stack.


    There's also link and unlk to build stack frames, not that they're often used.


    The 68K has some quite useful addressing modes, like:

    move.l $34(a0,d0.l),(a1)+

    -- Move the contents of memory location a0 + d0 + $34 to the memory location pointed to by a1 and increment a1 by the size of the data.

    On 68020 and above, you can scale the data register index:

    move...(a0,d0.l*4),...

    So you can index directly over byte, word, longs, or quads without having to pre-scale the index.

    With most addressing forms, you can replace an address register with pc, which can be useful for pc-relative relocatable code.
    but still there's some amazing things being done with the 68k. maybe someday I will be able to harness this power.

    The 68K seems to have attained a reputation for being one of the nicest instruction sets to actually program with (flexible, easy mnemonics, simple model, etc). It doesn't necessarily win any accolades for performance or beauty. ;-)
  • edited July 2007
    Yeah, it's quite nice to have so many different commands and addressing modes available to me (versus the 8 commands and four addressing modes of the pdp-8 ).

    So how do interrupts work on the 68k? the pdp-8 always saves the PC in location 0 and starts executing from location 1, where you can have a polling loop or whatever to deal with it. then the last instruction in the interrupt handling routine would be an indirect jump thrô location 0, taking the computer back to whatever it was doing before. But how does the 68k deal with it? And there are 8 levels of interrupt?
  • edited July 2007
    by the way, how could I go about writing something that adds a number to something else? that's fairly simple, except that I need it to overflow after 12 bits, and then start again from zero, regardless of the leftmost 4 bits...
  • edited July 2007
    wilsonsamm wrote: »
    So how do interrupts work on the 68k?

    The 68K has 8 interrupts and 8 interrupt levels. When one of the 8 interrupts is triggered, a vector address for that interrupt is fetched from a specific address in low memory. (On 68020 and above, the location of these addresses can change via a Vector Base Address register.) The 68K stacks an "exception" stack frame, which saves status registers and return address on the supervisor stack - not the same as the a7 user stack. (Assemblers use usp and ssp to differentiate when required.) It then jumps to the address fetched from the vector. You can return and reinstate status with an RTE instruction.

    Each interrupt routine has a priority equal to it's number. So "int 4" can interrupt "int 3" processing, but "int 2" cannot. Int 8 is actually an NMI and cannot be prevented.

    Interrupts on 68K are treated as "exceptions" along with illegal instructions, divide by zero, bus faults, traps, etc. Each has a vector for a support routine. Most programs run in user mode in which certain instructions are not allowed. When an exception is taken, the processor is in supervisor mode which has no restrictions. Nominally it is for OS vs. user programs and the security thereof. The 68K boots up in supervisor mode.
  • edited July 2007
    wilsonsamm wrote: »
    by the way, how could I go about writing something that adds a number to something else? that's fairly simple, except that I need it to overflow after 12 bits, and then start again from zero, regardless of the leftmost 4 bits...

    I think you mean:

    move.w (firstnumber),d0
    add.w (secondnumber),d0
    and.w #$0fff,d0 ;result in d0

    If you need to determine that is has overflowed, you'd need to mask and test for it explicitly, or forget about 12-bit masking and just shift all numbers left by 4 bits and use the carry flag.
  • edited July 2007
    Laser wrote: »
    move.w (firstnumber),d0
    add.w (secondnumber),d0
    and.w #$0fff,d0 ;result in d0

    If you need to determine that is has overflowed, you'd need to mask and test for it explicitly, or forget about 12-bit masking and just shift all numbers left by 4 bits and use the carry flag.
    I ended up doing this:
    ISZOP	cmpi	#$2,d0		ISZ
    	bne	DCAOP
    	move.w	d1,d2
    	addq	#$1,d2
    	and	#$1fff,d2
    	bclr	#$d,d1		test if we've overflown
    	bne	SKIPS		if yes, then skip next instruction.
    	ori	#$F000,d1	if not, then put 12 relevant bits into emued AC.
    	or	d2,d1
    	jmp	INTER
    SKIPS	ori	#$F000,d1	twelve leftmost bits in AC will always be 0000 in this case.
    	addq	#$1,a0		skip next instruction
    	jmp	INTER
    
    ISZ is an opcode which adds one to the memory location specified, and then skips the next instruction if it overflows after 4095.
    d0 holds the opcode we're currently emulating. Now the cmpi checks if it is indeed ISZ we're meant to be doing, and skips the entire routine if not. I do this with every opcode the pdp-8 has.
    INTER is the routine which (eventually) checks if an interrupt has occurred.
    AC is the emulated accumulator.

    well I ran my new code for the first time today. I found it does nothing but put a few nonsense bytes into registers d1, d2, d3. debug time, methinks.
  • edited July 2007
    Mhmm...
    I found out what was going wrong with my code: the command I use for fetching my opcode, posted below, only fetches a byte at a time, when I really need two.
    doesn't .w signify a word - 16 bits?
    OPFETCH	move.w	(A0)+,d0		put current opcode into d0. Increment PC.
    
  • edited July 2007
    wilsonsamm wrote: »
    the command I use for fetching my opcode, posted below, only fetches a byte at a time, when I really need two.
    doesn't .w signify a word - 16 bits?
    OPFETCH	move.w	(A0)+,d0		put current opcode into d0. Increment PC.
    
    That will indeed fetch 2 bytes from the memory at (a0) and then increment a0 by 2. The high 16 bits of d0 will be unaffected.

    A 68000 will throw a bus error if a0 isn't word-aligned when you try that. 68020 up can fetch misaligned data.

    Edit:
    Bear in mind also that 68K is big-endian. The lowest-address byte will be the top 8 bits of the 16-bit word. Opposite to our beloved Speccy Z80 CPU.
  • edited July 2007
    wilsonsamm wrote: »
    I ended up doing this:
    ISZOP	cmpi	#$2,d0		ISZ
    	bne	DCAOP
    	move.w	d1,d2
    	addq	#$1,d2
    	and	#$1fff,d2
    	bclr	#$d,d1		test if we've overflown
    	bne	SKIPS		if yes, then skip next instruction.
    	ori	#$F000,d1	if not, then put 12 relevant bits into emued AC.
    	or	d2,d1
    	jmp	INTER
    SKIPS	ori	#$F000,d1	twelve leftmost bits in AC will always be 0000 in this case.
    	addq	#$1,a0		skip next instruction
    	jmp	INTER
    
    ISZ is an opcode which adds one to the memory location specified, and then skips the next instruction if it overflows after 4095.
    d0 holds the opcode we're currently emulating. Now the cmpi checks if it is indeed ISZ we're meant to be doing, and skips the entire routine if not. I do this with every opcode the pdp-8 has.
    INTER is the routine which (eventually) checks if an interrupt has occurred.
    AC is the emulated accumulator.

    I'm not too sure what you're up to with the ORI instructions there. Particularly:

    "SKIPS ori #$F000,d1 twelve leftmost bits in AC will always be 0000 in this case."

    The leftmost 4 bits of d1 become 1111 if you do that, and the rightmost 12 are left alone. :-?

    You also appear to be operating on the data in d2 at one moment, then d1 the next. Apologies if I haven't understood what you're trying to do.

    I'd have done something like:
        addq.w  #1,d1
        move.w  d1,d2
        and.w   #$0fff,d1
        cmp.w   d1,d2
        beq     NoSkip
    Skip:
        ....
    

    And since you're only ever adding one, maximum, you can get away with:
        addq.w  #1,d1
        and.w   #$0fff,d1
        bne     NoSkip
    Skip:
        ....
    

    As I say, I'm sorry I don't understand what all the results are, and in which registers, that you want here.


    BTW, most emulators wouldn't go through a chain of cmpi/bne to decide what instruction to emulate. You'd use a jump table. Or is your cmpi some sort of error check?
  • edited July 2007
    thanks for the advice, I incorporated bits and bobs in the source.
    I'm posting the source here, incase anyone's interested. At the beginning there's a small pdp-8 routine, which is then run by the emulator which starts at 1000. This routine does this:
    0000 1007 tad   0007  ;add to AC whatever's in location 7
    0001 3406 dca i 0006   ;deposit AC to the location pointed to by 0006, and clear AC.
    0002 2006 isz   0006   ;increment location 0006 by one, and skip next instruction if 0
    0003 5000 jmp   0000   ;jump to location 0000
    0004 7402 hlt          ;halt processor.
    0005 
    0006 0010
    0007 5252
    0010 ; first location to be written to
    
    * pppp     d pppp   888     / ee   mm mm  
    * p   p    d p   p 8   8   / e  e m  m  m 
    * pppp  dddd pppp   888   /  eee  m  m  m 
    * p    d   d p     8   8 /   e    m  m  m 
    * p     dddd p      888 /     eee m  m  m 
    *
    * by  s a m.  f r.  j u l.  2 0.  2 0 0 7.
    * version zero. attempts to run pdp-8 code.
    
    memloc0	dc.w	$0207		* tad   0007
    memloc1	dc.w	$0706		* dca i 0006
    memloc2	dc.w	$0406		* isz   0006
    memloc3	dc.w	$0a00		* jmp   0000
    memloc4	dc.w	$0F02		* hlt
    memloc5	dc.w	$0000
    memloc6	dc.w	$0008		* starting adress
    memloc7	dc.w	$0aaa		* fill pattern
    START	org	$1000
    OPFETCH	move.w	(a0)+,d0		put current opcode into d0.
    	move	d0,d2		time to calculate what adress is being reffed to
    	andi.w	#$FF,d2
    	asl	#$1,d2		make sure we've got an even adress.
    	bclr	#$8,d2		zero page?
    	beq	DIRECTION		yup. that's what we have now, so see if we want indirection.
    	move	a0,d3		otherwise, get page from upper 5 bits of PC.
    	ori.w	#$FF80,d3
    	or.w	d3,d2
    DIRECTION	btst	#$9,d0		now, are we dealing with indirect adressing?
    	beq	INDIRECT		yes, so do it.
    	move	d2,a1		otherwise, put address into appropriate register
    	jmp	OPPARSE		and see what's next to go down inda hood.
    INDIRECT	move.w	d2,a2
    	move.w	(a2),a1		use a2 as scratchpad for loading effective adress into adress register
    OPPARSE	asr	#$8,d0		get rid of everything but the command.
    	asr	#$1,d0		we have everything we need.
    	cmpi	#$0,d0
    	beq	ANDOP
    	cmpi	#$1,d0
    	beq	TADOP
    	cmpi	#$2,d0
    	beq	ISZOP
    	cmpi	#$3,d0
    	beq	DCAOP
    	cmpi	#$4,d0
    	beq	JMSOP
    	cmpi	#$5,d0
    	beq	JMPOP
    	cmpi	#$6,d0
    	beq	IOTOP
    	cmpi	#$7,d0
    	beq	OPROP
    ANDOP	move	(a1),d2		AND - self explanatory
    	and	d2,d1
    	jmp	INTER
    TADOP	add	(a1),d1		TAD - add operand to accumulator
    	jmp	INTER
    ISZOP	move.w	(a1),d2		ISZ - increment operand by 1. Skip next instruction if it overflows to 0
    	addq.w	#$1,d2
    	andi.w	#$0fff,d2
    	bne	NOSKIP
    	addq	#$2,a0
    NOSKIP	andi.w	#$f000,(a1)		rightmost twelve bits will always be 0 in this case
    	or.w	d2,(a1)
    	jmp	INTER
    DCAOP	move.w	d1,(a1)		DCA - deposit and clear accumulator
    	move.w	#$0,d1
    	jmp	INTER
    JMSOP	move	a0,(a1)+		JMS - store PC at word
    	move	a1,a0		and continue execution from the following word
    	addq	#$2,a0		execute next command after that.
    	jmp	OPFETCH		in this case, don't allow an interrupt to be dealt with till next round
    JMPOP	move.w	a1,a0		JMP - jump
    	jmp	INTER
    IOTOP	addq	#$2,a0
    	jmp	INTER		IOT - input output transfers.
    OPROP	addq	#$2,a0		OPR - operations.
    	jmp	INTER
    INTER	nop			deal with interrupts. fill this in later
    	jmp	opfetch		
    
    	MOVE.B	#9,D0
    	TRAP	#15		Halt Simulator
    
    	END	START
    
    the first few instructions run ok, but the third fails because of a mistake in the routine for handling indirect addressing. Can someone see what the mistake is?
  • edited July 2007
    I sussed it! :)
    When the adress is direct, there's a command which says "asl.w #$1,d2". this basically doubles the adress, so that this number is the adress in bytes, like the 68k does it, not words as does the PDP-8. This of course, has to be done again if it's indirect adressing as well.
  • edited July 2007
    Unless you really need the sign-preservation of ASL, use LSL. However, a quicker way of LSL #1,Dn is ADD Dn,Dn.

    Glad you're getting confident enough with 68K to find bugs in your code. :-)
Sign In or Register to comment.