Tuesday, May 31, 2016

IRIS Assembly Language (Part 2)

This page is a part of the "Understanding IRIS" collection.  Many thanks to Tom Trebisky, for figuring this out, and sharing this with us:

------------------------------------------------------------------

Our first page on IRIS Disassembly can be found here.

Please consider this page an additional chapter into the disassembly and deeper study of the Point 4 IRIS assembly language, and various example programs.

To start with, Tom has documented well a high-level of the assembly language of this system, in his web page:

The Data General Nova

I will try to repeat very little of what is here, only to add details as I learn them along the way, and possibly to re-iterate items from Tom's page on this topic, in a different way, which helped me grasp these concepts and details.

------------------------------------------------------------------

The files which are the focus of this disassembly project are from the Microtech Dart Utility, bootable tape.  Details and all source files can be downloaded from here:

Microtech Dart Utility Tape

-------------------------------------------------------------------

A breakthrough !!  At last something that looks like real code!!!!

The following is from my disassembly of file01 (the alleged boot loader).  Just for the record, I was suspicious this
was a boot loader all along.  I mean, what else would you put as the first record of a tape, and what else would
be only 512 bytes.

0050:   31f0    030760  1p      lda             r2,0040         ; 000100, pc-10
0051:   39f0    034760  9p      lda             r3,0041         ; 000101, pc-10
0052:   2200    021000  "       lda             r0,r2+00
0053:   4300    041400  C       sta             r0,r3+00
0054:   d300    151400  S       inc             r2,r2
0055:   fb00    175400  {       inc             r3,r3
0056:   19ec    014754   l      dsz             0042            ; 000102, pc-14
0057:   01fb    000773   {      jmp             0052            ; 000122, pc-05

Two things led to this breakthrough.  One was fixing an address display bug in my disassembler, the other
was using the byte order as you originally sent me.  I was trying both endiannesses but was biased towards
swapping what you had sent based on file 14 or 15 showing text properly when I swapped it, but clearly you
had the byte order right for this file (but we know there are some exceptions later).

And a note for Alan -- I have deviated from the usual Nova assembler syntax.  I display values as
hex (not octal) and indicate registers as r0, .... not just a number or ac3.

The first 4 columns above are:
 -- address in hex
 -- contents in hex
 -- contents in octal
 -- contents as ascii (with high bit cleared)

Anyway, here is a play by play on the above:

1) load register r2 from address 0x0040 (this loads zero)
2) load register r3 from address 0x0041 (this loads 0x6d00)
now a loop begins
3) load register r0 from the address pointed to by r2
4) store register r0 to the address pointed to by r3
5,6) increment r2 and r3
7) decrement and skip if zero on location 42.
This initially has the value 0x100 (a count of 256)
8) jump to keep the loop going.

So what is this doing?  It is a block copy of 256 words (512 bytes) starting from address 0000
and depositing it at address 0x6d00.

So -- this is where I had hoped to get all day yesterday; a piece of assembly language that I could comment on
an explain to get your "pump primed".

I am so happy.

And you need to comment on how you like the format.  Changes are easy to make.
I have contemplated putting 0x in front of all hex, but think it will just make a mess.
Also some way to indicate octal (I am tempted to use a leading % sign).

Note that for a pc relative address, I calculate the actual address and display it (as in jmp 0052).
I could care less if it was encoded as a PC relative value, I want to look at the code and know where
I am jumping.  In the comment I put the octal value and the pc-05 thing for the curious, though I
don't really know what purpose that might serve.

-------------------------------------------------------------

Here is the entire disassembly of the 256 word boot loader (file01), which sort of showcases
the current disassembler output.  The disassembler itself is only 179 lines of ruby.  I will send
it along as well once I think it is semi-stable.  I am waiting to see if Alan screams when he sees
the Nova syntax I am generating.  I added options to switch off the column of ascii display
as well as the octal, so this output is kinda "lean and mean".  I did add the "%" prefix to all
octal values and decided to display the device codes for the IO instructions in octal since
the Nova manual always seems to cite these as octal.  You will see the leading "%" for these.
Everything else is hex (we don't want confusion about what base things are displayed in).

------------------------------------------------------------

For anyone who actually used to look at and handle Point-4 computers, here is a question for you:

How do they boot up?  I mean if you shove a tape in, how do you make it boot from tape?
What insights do you have about how this 256 word loader would get loaded?
Why does the first bit of real code seem to be at address 0x50, does that ring any
bells with you?

I would expect this thing to get loaded into address zero and the PC get set to 0 to run,
but if execution started at address 0, it would run a bunch of docp instructions, then
at address 0x40 hit a jump back to zero, hence running some endless unwrapped loop
of just docp instructions.   It is odd that the value 0x7eff appears like padding at the
start and end of this file or block or record or whatever you want to call it.

The end of the block may have a clue.  It ends with an indirect jump through 0x00fe
which would take it to the loop I found at address 0x0050.  Is this some defined Nova
behavior?  Load a 256 word block and then set the PC to run the instruction in the
last word?  In that case, why would it not just be a jump to 0050 itself?

------------------------------------------------------------

Nova boot loader annotation

OK, I studied my disassembly of the Nova bootloader, and as I like to do when studying some
disassembled code, annotated it with comments.

Also found a bug in the disassembler (recently introduced) and added an option to disassemble
at a specified address (as was needed to handle the address relocation).

------------------------------------------------------------

Disassembled [Microtech Dart Utility] file 2 and working on it.
It makes good sense also.
It has text strings.  It will be curious to see how they get printed.

Also, I just learned that the order of registers in ALU instructions is not
what I expect (you probably don't have any expectations on this).
So if you have:

add    r1, r2

It adds r1+r2 and puts the result in r2 (other assemblers have the destination register first).
Well this order matches the register encoding in the instruction, not that that matters.

Actually as long as we KNOW what is first, we are OK.  Likewise

mov    r1, r2

moves a value from r1 to r2 (i.e r2=r1 gets done).

   Tom

------------------------------------------------------------

#!/bin/ruby

# Nova (Point-4) Disassembler
# Tom Trebisky  5-30-2016
#
# At this time, you must enter the input filename below.
# also note the true/false options to enable extra
#  stuff in the output.
# Output to stdout.

#infile = "Files/nova15.bin"
#infile = "Files/nova02.bin"
infile = "Files/nova01.bin"

# 22 has strings indicating it is a Point 4 disk utility
#infile = "Files/nova22.bin"
#infile = "nova22.swab"

$show_octal = false
$show_ascii = false
$show_pcrel = false

$swap_bytes = false

$start_address = 0x6d00

# -----------------------------------------------------
# -----------------------------------------------------

# This yields a big string
buf = IO.binread ( infile )

# This yields an array of 16 bit items
if ( $swap_bytes )
    words = buf.unpack('n*')
else
    #words = buf.unpack('S*')
    words = buf.unpack('v*')
end

$io_ops = %w( nio dia doa dib dob dic doc skp )
$skp_ctrl = %w( bn bz dn dz )
$xfer_ctrl = [ " ", "s", "c", "p" ]

def io_decode ( w )
    op = (w >> 8) & 0x7
    sop = $io_ops[op]

    ctrl = (w >> 6) & 0x3
    if ( op == 7 )
sop += $skp_ctrl[ctrl]
    else
sop += $xfer_ctrl[ctrl]
    end
    reg = (w >> 11) & 0x3
    sreg = "r#{reg}"
    dev = "%" + "%o" % (w & 0x3f)

    return sop + "\t\t" + sreg + "," + dev
end

$alu_ops = %w( com neg mov inc adc sub add and )
$carry_l = [ "", "z", "o", "c" ]
$shift_l = [ "", "l", "r", "s" ]
$sk_ops = %w( --- skp szc snc szr snr sez sbn )

def alu_decode ( w )
    rs = "r%d" % ((w>>13) & 0x3)
    rd = "r%d" % ((w>>11) & 0x3)

    op = (w>>8) & 0x7
    sop = $alu_ops[op]
    carry = (w>>4) & 0x3
    sop += $carry_l[carry]
    shift = (w>>6) & 0x3
    sop += $shift_l[shift]

    if ( ( w & 0x8 ) == 0x8 )
sop += "#"
    end
    sop += " " if ( carry == 0 )
    sop += " " if ( shift == 0 )

    sk = w & 0x7
    if ( sk != 0 )
return sop + "\t\t" + rs + "," + rd + "," + $sk_ops[sk]
    else
return sop + "\t\t" + rs + "," + rd
    end
end

def to_signed ( uval )
    return uval if uval < 128
    return uval - 256
end

def to_octal ( val )
    return "%" + "%06o" % val
end

def mem_addr ( w )
    rv = ""
    if ( (w & 0x400) == 0x400 )
rv = "@"
    end

    udisp = w & 0xff
    bdisp = to_signed udisp
    # print "#{udisp} #{bdisp}\n"
    if ( bdisp < 0 )
disp = "-%02x" % -bdisp
    else
disp = "+%02x" % bdisp
    end
    if ( (w & 0x300) == 0 )
# absolute address.
uaddr = "%04x" % udisp
if ( $show_octal )
   return rv + uaddr + "\t\t; " + to_octal( udisp )
else
   return rv + uaddr
end
    elsif ( w & 0x300 == 0x100 )
# PC relative address
pcaddr = $addr + bdisp
spcaddr = "%04x" % pcaddr
rv += spcaddr
if ( $show_octal )
   opcaddr = to_octal pcaddr
   rv += "\t\t; " + opcaddr
end
if ( $show_pcrel )
   if ( $show_octal )
rv += ", pc" + disp
   else
rv += "\t\t; pc" + disp
   end
end
return rv
    elsif ( w & 0x300 == 0x200 )
return rv + "r2" + disp
    else
return rv + "r3" + disp
    end
end

def ls_addr ( w )
    reg = (w >> 11) & 0x3
    return "r#{reg}," + mem_addr( w )
end

$mem_ops = %w( jmp jsr isz dsz lda lda lda lda sta sta sta sta )

def mem_decode ( w )
    op = (w>>11) & 0xf
    if ( op < 4 )
return $mem_ops[op] + "\t\t" + mem_addr( w )
    else
return $mem_ops[op] + "\t\t" + ls_addr( w )
    end
end

def decode ( w )
    if ( w & 0xe000 == 0x6000 )
return io_decode ( w )
    elsif ( w & 0x8000 == 0x8000 )
return alu_decode ( w )
    else
return mem_decode ( w )
    end
end

def show_ascii ( w )
    b1 = (w >> 8) & 0x7f
    b2 = w & 0x7f
    c1 = b1.chr
    c2 = b2.chr
    c1 = " " if ( b1 <= 0x20 or b1 >= 0x7f )
    c2 = " " if ( b2 <= 0x20 or b2 >= 0x7f )
    return c1 + c2
end

$addr = $start_address

words.each { |w|
    ad = "%04x" % $addr
    ww = "%04x" % w
    outline = ad + ":\t" + ww + "\t"
    if ( $show_octal )
ow = to_octal w
outline += ow + "/t"
    end
    if ( $show_ascii )
cc = show_ascii w
outline += cc + "/t"
    end
    ins = decode w
    outline += "\t" + ins + "\n"
    print outline

    $addr += 1
}

# THE END


------------------------------------------------------------------

This page is a part of the "Understanding IRIS" collection.  

No comments:

Post a Comment