summaryrefslogtreecommitdiff
path: root/support/scripts/z80-disasm.pl
diff options
context:
space:
mode:
authorXavier ASUS <xavi92psx@gmail.com>2019-10-18 00:31:54 +0200
committerXavier ASUS <xavi92psx@gmail.com>2019-10-18 00:31:54 +0200
commit268a53de823a6750d6256ee1fb1e7707b4b45740 (patch)
tree42c1799a9a82b2f7d9790ee9fe181d72a7274751 /support/scripts/z80-disasm.pl
downloadsdcc-gas-268a53de823a6750d6256ee1fb1e7707b4b45740.tar.gz
sdcc-3.9.0 fork implementing GNU assembler syntax
This fork aims to provide better support for stm8-binutils
Diffstat (limited to 'support/scripts/z80-disasm.pl')
-rwxr-xr-xsupport/scripts/z80-disasm.pl4959
1 files changed, 4959 insertions, 0 deletions
diff --git a/support/scripts/z80-disasm.pl b/support/scripts/z80-disasm.pl
new file mode 100755
index 0000000..24d4f00
--- /dev/null
+++ b/support/scripts/z80-disasm.pl
@@ -0,0 +1,4959 @@
+#!/usr/bin/perl -w
+
+=back
+
+ Copyright (C) 2013-2016, Molnar Karoly <molnarkaroly@users.sf.net>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any source distribution.
+
+================================================================================
+
+ This program disassembles the hex files. It assumes that the hex file
+ contains Z80 instructions.
+
+ Proposal for use: ./z80-disasm.pl program.hex > program.dasm
+
+ $Id: z80-disasm.pl 9450 2016-01-09 16:47:43Z molnarkaroly $
+=cut
+
+use strict;
+use warnings;
+no if $] >= 5.018, warnings => "experimental::smartmatch"; # perl 5.16
+use 5.12.0; # when (regex)
+
+use constant FALSE => 0;
+use constant TRUE => 1;
+
+use constant TAB_LENGTH => 8;
+
+################################################################################
+
+use constant INHX8M => 0;
+use constant INHX32 => 2;
+
+use constant INHX_DATA_REC => 0;
+use constant INHX_EOF_REC => 1;
+use constant INHX_EXT_LIN_ADDR_REC => 4;
+
+use constant EMPTY => -1;
+
+use constant COUNT_SIZE => 2;
+use constant ADDR_SIZE => 4;
+use constant TYPE_SIZE => 2;
+use constant BYTE_SIZE => 2;
+use constant CRC_SIZE => 2;
+use constant HEADER_SIZE => (COUNT_SIZE + ADDR_SIZE + TYPE_SIZE);
+use constant MIN_LINE_LENGTH => (HEADER_SIZE + CRC_SIZE);
+
+use constant Z80_ROM_SIZE => 0x10000;
+
+################################################################################
+
+my $PROGRAM = 'z80-disasm.pl';
+
+my $border0 = ('-' x 99);
+my $border1 = ('#' x 99);
+my $border2 = ('.' x 39);
+
+my @default_paths =
+ (
+ '/usr/share/sdcc/include/z180',
+ '/usr/local/share/sdcc/include/z180'
+ );
+
+my $default_include_path = '';
+my $include_path = '';
+my $hex_file = '';
+my $map_file = '';
+my $map_readed = FALSE;
+my $header_file = '';
+my $name_list = '';
+
+my $verbose = 0;
+my $gen_assembly_code = FALSE;
+my $no_explanations = FALSE;
+my $find_lost_labels = FALSE;
+
+my @rom = ();
+my $rom_size = Z80_ROM_SIZE;
+my %const_areas_by_address = (); # From the command line parameters.
+
+my %const_blocks_by_address = ();
+
+my %ram_blocks_by_address = ();
+my %ram_names_by_address = ();
+
+=back
+ The structure of one element of the %io_by_address hash:
+
+ {
+ NAME => '',
+ REF_COUNT => 0
+ }
+=cut
+
+my %io_by_address = ();
+
+ # Sizes of the instructions.
+
+use constant IPREFIX_DD => -1;
+use constant IPREFIX_ED => -2;
+use constant IPREFIX_FD => -3;
+
+my @instruction_sizes_ =
+ (
+# 0 1 2 3 4 5 6 7 8 9 A B C D E F
+
+ 1, 3, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, # 00
+ 2, 3, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1, # 10
+ 2, 3, 3, 1, 1, 1, 2, 1, 2, 1, 3, 1, 1, 1, 2, 1, # 20
+ 2, 3, 3, 1, 1, 1, 2, 1, 2, 1, 3, 1, 1, 1, 2, 1, # 30
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 40
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 50
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 60
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 70
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 80
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 90
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # A0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # B0
+ 1, 1, 3, 3, 3, 1, 2, 1, 1, 1, 3, 2, 3, 3, 2, 1, # C0
+ 1, 1, 3, 2, 3, 1, 2, 1, 1, 1, 3, 2, 3,IPREFIX_DD, 2, 1, # D0 -1: DD
+ 1, 1, 3, 1, 3, 1, 2, 1, 1, 1, 3, 1, 3,IPREFIX_ED, 2, 1, # E0 -2: ED
+ 1, 1, 3, 1, 3, 1, 2, 1, 1, 1, 3, 1, 3,IPREFIX_FD, 2, 1 # F0 -3: FD
+ );
+
+my @instruction_sizes_DDFD =
+ (
+# 0 1 2 3 4 5 6 7 8 9 A B C D E F
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, # 00
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, # 10
+ 0, 4, 4, 2, 2, 2, 3, 0, 0, 2, 4, 2, 2, 2, 3, 0, # 20
+ 0, 0, 0, 0, 3, 3, 4, 0, 0, 2, 0, 0, 0, 0, 0, 0, # 30
+ 0, 0, 0, 0, 2, 2, 3, 0, 0, 0, 0, 0, 2, 2, 3, 0, # 40
+ 0, 0, 0, 0, 2, 2, 3, 0, 0, 0, 0, 0, 2, 2, 3, 0, # 50
+ 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, # 60
+ 3, 3, 3, 3, 3, 3, 0, 3, 0, 0, 0, 0, 2, 2, 3, 0, # 70
+ 0, 0, 0, 0, 2, 2, 3, 0, 0, 0, 0, 0, 2, 2, 3, 0, # 80
+ 0, 0, 0, 0, 2, 2, 3, 0, 0, 0, 0, 0, 2, 2, 3, 0, # 90
+ 0, 0, 0, 0, 2, 2, 3, 0, 0, 0, 0, 0, 2, 2, 3, 0, # A0
+ 0, 0, 0, 0, 2, 2, 3, 0, 0, 0, 0, 0, 2, 2, 3, 0, # B0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, # C0 4: CB
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # D0
+ 0, 2, 0, 2, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, # E0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0 # F0
+ );
+
+my @instruction_sizes_ED =
+ (
+# 0 1 2 3 4 5 6 7 8 9 A B C D E F
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 00
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 10
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 20
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 30
+ 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, # 40
+ 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, # 50
+ 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, # 60
+ 2, 2, 2, 4, 2, 2, 2, 0, 2, 2, 2, 4, 2, 2, 2, 0, # 70
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 80
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 90
+ 2, 2, 2, 2, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, # A0
+ 2, 2, 2, 2, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, # B0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # C0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # D0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # E0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 # F0
+ );
+
+my $prev_is_jump;
+
+use constant SILENT0 => 0;
+use constant SILENT1 => 1;
+
+my $decoder_silent_level;
+
+use constant RAM_ALIGN_SIZE => 3;
+use constant EXPL_ALIGN_SIZE => 5;
+use constant STAT_ALIGN_SIZE => 6;
+use constant TBL_COLUMNS => 8;
+
+=back
+ The structure of one element of the %blocks_by_address hash:
+
+ {
+ TYPE => 0,
+ ADDR => 0,
+ SIZE => 0,
+ LABEL => {
+ TYPE => 0,
+ NAME => '',
+ PRINTED => FALSE,
+ CALL_COUNT => 0,
+ JUMP_COUNT => 0
+ },
+ REF_COUNT => 0
+ }
+=cut
+
+use constant BLOCK_INSTR => 0;
+use constant BLOCK_RAM => 1;
+use constant BLOCK_CONST => 2;
+use constant BLOCK_EMPTY => 3;
+
+use constant BL_TYPE_NONE => -1;
+use constant BL_TYPE_SUB => 0;
+use constant BL_TYPE_LABEL => 1;
+use constant BL_TYPE_JTABLE => 2;
+use constant BL_TYPE_VARIABLE => 3;
+use constant BL_TYPE_CONST => 4;
+
+my %label_names =
+ (
+ eval BL_TYPE_SUB => 'Function_',
+ eval BL_TYPE_LABEL => 'Label_',
+ eval BL_TYPE_JTABLE => 'Jumptable_',
+ eval BL_TYPE_VARIABLE => 'Variable_',
+ eval BL_TYPE_CONST => 'Constant_'
+ );
+
+my %empty_blocks_by_address = ();
+my %blocks_by_address = ();
+my %labels_by_address = ();
+my $max_label_addr = 0;
+
+my %interrupts_by_address =
+ (
+ 0x0000 => 'System_init',
+ 0x0008 => 'Interrupt_08',
+ 0x0010 => 'Interrupt_10',
+ 0x0018 => 'Interrupt_18',
+ 0x0020 => 'Interrupt_20',
+ 0x0028 => 'Interrupt_28',
+ 0x0030 => 'Interrupt_30',
+ 0x0038 => 'Interrupt_38'
+ );
+
+my %control_characters =
+ (
+ 0x00 => '\0',
+ 0x07 => '\a',
+ 0x08 => '\b',
+ 0x09 => '\t',
+ 0x0A => '\n',
+ 0x0C => '\f',
+ 0x0D => '\r',
+ 0x1B => '\e',
+ 0x7F => '^?'
+ );
+
+use constant INST_LD_HL => 0x21;
+use constant INST_ADD_HL_DE => 0x19;
+use constant INST_JP => 0xC3;
+use constant INST_JP_HL => 0xE9;
+use constant INST_JP_CC => 0xC2; # mask: 0xC7
+use constant INST_JR => 0x18;
+use constant INST_JR_CC => 0x20; # mask: 0xE7
+use constant INST_DJNZ => 0x10;
+use constant INST_CALL => 0xCD;
+use constant INST_CALL_CC => 0xC4; # mask: 0xC7
+use constant INST_RET => 0xC9;
+use constant INST_RETI => 0x4D; # with 0xED prefix
+use constant INST_RETN => 0x45; # with 0xED prefix
+
+my $dcd_address = 0;
+my $dcd_instr_size = 0;
+my $dcd_instr = 0;
+my $dcd_instr_x = 0;
+my $dcd_instr_y = 0;
+my $dcd_instr_z = 0;
+my $dcd_instr_p = 0;
+my $dcd_instr_q = 0;
+my $dcd_parm0 = 0;
+my $dcd_parm1 = 0;
+my $dcd_parm2 = 0;
+
+my $table_header = '';
+my $table_border = '';
+
+################################################################################
+################################################################################
+
+my %pp_defines = (); # Value of definitions.
+
+my @pp_conditions = ();
+my @pp_else_conditions = ();
+my $pp_level = 0; # Shows the lowest level.
+my $embed_level;
+
+# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+#@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@
+#@@@@@@@@@@@@@@@@@@@@@@@ This a simple preprocessor. @@@@@@@@@@@@@@@@@@@@@@@@@
+#@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@
+# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+
+ # Examines that the parameter is defined or not defined.
+
+sub _defined($)
+ {
+ return defined($pp_defines{$_[0]});
+ }
+
+#-------------------------------------------------------------------------------
+
+ # Records a definition.
+
+sub define($)
+ {
+ my ($Name) = ($_[0] =~ /^(\S+)/op);
+ my $Body = ${^POSTMATCH};
+
+ $Body =~ s/^\s+//o;
+
+ die "define(): This definition already exists: \"$Name\"\n" if (_defined($Name));
+
+ # The definition is in fact unnecessary.
+ $pp_defines{$Name} = $Body;
+ }
+
+#-------------------------------------------------------------------------------
+
+ # Delete a definition.
+
+sub undefine($)
+ {
+ delete($pp_defines{$_[0]});
+ }
+
+#-------------------------------------------------------------------------------
+
+ # Evaluation of the #if give a boolean value. This procedure preserves it.
+
+sub if_condition($)
+ {
+ my $Val = $_[0];
+
+ push(@pp_conditions, $Val);
+ push(@pp_else_conditions, $Val);
+ ++$pp_level;
+ }
+
+#-------------------------------------------------------------------------------
+
+ # Evaluation of the #else give a boolean value. This procedure preserves it.
+
+sub else_condition($$)
+ {
+ my ($File, $Line_number) = @_;
+
+ die "else_condition(): The ${Line_number}th line of $File there is a #else, but does not belong him #if.\n" if ($pp_level <= 0);
+
+ my $last = $#pp_conditions;
+
+ if ($last > 0 && $pp_conditions[$last - 1])
+ {
+ $pp_conditions[$last] = ($pp_else_conditions[$#pp_else_conditions]) ? FALSE : TRUE;
+ }
+ else
+ {
+ $pp_conditions[$last] = FALSE;
+ }
+ }
+
+#-------------------------------------------------------------------------------
+
+ # Closes a logical unit which starts with a #if.
+
+sub endif_condition($$)
+ {
+ my ($File, $Line_number) = @_;
+
+ die "endif_condition(): The ${Line_number}th line of $File there is a #endif, but does not belong him #if.\n" if ($pp_level <= 0);
+
+ pop(@pp_conditions);
+ pop(@pp_else_conditions);
+ --$pp_level;
+ }
+
+#-------------------------------------------------------------------------------
+
+sub reset_preprocessor()
+ {
+ %pp_defines = ();
+ @pp_conditions = ();
+ push(@pp_conditions, TRUE);
+ @pp_else_conditions = ();
+ push(@pp_else_conditions, FALSE);
+ $pp_level = 0;
+ }
+
+#-------------------------------------------------------------------------------
+
+ # This the preprocessor.
+
+sub run_preprocessor($$$$)
+ {
+ my ($Fname, $Function, $Line, $Line_number) = @_;
+
+ if ($Line =~ /^#\s*ifdef\s+(\S+)$/o)
+ {
+ if ($pp_conditions[$#pp_conditions])
+ {
+ # The ancestor is valid, therefore it should be determined that
+ # the descendants what kind.
+
+ if_condition(_defined($1));
+ }
+ else
+ {
+ # The ancestor is invalid, so the descendants will invalid also.
+
+ if_condition(FALSE);
+ }
+ }
+ elsif ($Line =~ /^#\s*ifndef\s+(\S+)$/o)
+ {
+ if ($pp_conditions[$#pp_conditions])
+ {
+ # The ancestor is valid, therefore it should be determined that
+ # the descendants what kind.
+
+ if_condition(! _defined($1));
+ }
+ else
+ {
+ # The ancestor is invalid, so the descendants will invalid also.
+
+ if_condition(FALSE);
+ }
+ }
+ elsif ($Line =~ /^#\s*else/o)
+ {
+ else_condition($Fname, $Line_number);
+ }
+ elsif ($Line =~ /^#\s*endif/o)
+ {
+ endif_condition($Fname, $Line_number);
+ }
+ elsif ($Line =~ /^#\s*define\s+(.+)$/o)
+ {
+ # This level is valid, so it should be recorded in the definition.
+
+ define($1) if ($pp_conditions[$#pp_conditions]);
+ }
+ elsif ($Line =~ /^#\s*undef\s+(.+)$/o)
+ {
+ # This level is valid, so it should be deleted in the definition.
+
+ undefine($1) if ($pp_conditions[$#pp_conditions]);
+ }
+ elsif ($pp_conditions[$#pp_conditions])
+ {
+ # This is a valid line. (The whole magic is in fact therefore there is.)
+
+ $Function->($Line);
+ }
+ }
+
+################################################################################
+################################################################################
+################################################################################
+
+sub basename($)
+ {
+ return ($_[0] =~ /([^\/]+)$/) ? $1 : '';
+ }
+
+#-------------------------------------------------------------------------------
+
+sub param_exist($$)
+ {
+ die "This option \"$_[0]\" requires a parameter.\n" if ($_[1] > $#ARGV);
+ }
+
+#-------------------------------------------------------------------------------
+
+sub Log
+ {
+ return if (pop(@_) > $verbose);
+ foreach (@_) { print STDERR $_; }
+ print STDERR "\n";
+ }
+
+#-------------------------------------------------------------------------------
+
+sub str2int($)
+ {
+ my $Str = $_[0];
+
+ return hex($1) if ($Str =~ /^0x([[:xdigit:]]+)$/io);
+ return int($Str) if ($Str =~ /^-?\d+$/o);
+
+ die "str2int(): This string not integer: \"$Str\"";
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Before print, formats the $Text.
+ #
+
+sub align($$)
+ {
+ my ($Text, $Tab_count) = @_;
+ my ($al, $ct);
+
+ $ct = $Text;
+ 1 while $ct =~ s/\t/' ' x (TAB_LENGTH - ($-[0] % TAB_LENGTH))/e;
+ $al = $Tab_count - (int(length($ct) / TAB_LENGTH));
+
+ # One space will surely becomes behind it.
+ if ($al < 1)
+ {
+ return "$Text ";
+ }
+ else
+ {
+ return ($Text . ("\t" x $al));
+ }
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Multiple file test.
+ #
+
+sub is_file_ok($)
+ {
+ my $File = $_[0];
+
+ if (! -e $File)
+ {
+ print STDERR "$PROGRAM: Not exists -> \"$File\"\n";
+ exit(1);
+ }
+
+ if (! -f $File)
+ {
+ print STDERR "$PROGRAM: Not file -> \"$File\"\n";
+ exit(1);
+ }
+
+ if (! -r $File)
+ {
+ print STDERR "$PROGRAM: Can not read -> \"$File\"\n";
+ exit(1);
+ }
+
+ if (! -s $File)
+ {
+ print STDERR "$PROGRAM: Empty file -> \"$File\"\n";
+ exit(1);
+ }
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Initializes the @rom array.
+ #
+
+sub init_mem($$)
+ {
+ my ($Start, $End) = @_;
+
+ @rom[$Start .. $End] = ((EMPTY) x ($End - $Start + 1));
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Store values of the $Code to $AddrRef address.
+ #
+
+sub store_code($$)
+ {
+ my ($Code, $AddrRef) = @_;
+
+ if ($$AddrRef >= $rom_size)
+ {
+ printf STDERR "Warning, this address (0x%04X) outside the code area (0x%04X)!\n", $$AddrRef, $rom_size - 1;
+ }
+
+ Log(sprintf("rom[0x%08X] = 0x%02X", $$AddrRef, $Code), 9);
+ $rom[$$AddrRef++] = $Code;
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Reads contents of the $Hex.
+ #
+
+sub read_hex($)
+ {
+ my $Hex = $_[0];
+ my $addr_H = 0;
+ my $format = INHX32;
+ my $line_num = 0;
+
+ if (! open(IN, '<', $Hex))
+ {
+ print STDERR "$PROGRAM : Could not open. -> \"$Hex\"\n";
+ exit(1);
+ }
+
+ while (<IN>)
+ {
+ chomp;
+ s/\r$//o;
+ ++$line_num;
+
+ my $len = length() - 1;
+
+ if ($len < MIN_LINE_LENGTH)
+ {
+ close(IN);
+ print STDERR "$PROGRAM: ${line_num}th line <- Shorter than %u character.\n", MIN_LINE_LENGTH;
+ exit(1);
+ }
+
+ Log("$..(1) (\"$_\") length() = " . length(), 7);
+
+ my $bytecount = int(($len - MIN_LINE_LENGTH) / BYTE_SIZE);
+
+ my $binrec = pack('H*', substr($_, 1));
+
+ if (unpack('%8C*', $binrec) != 0)
+ {
+ close(IN);
+ print STDERR "$PROGRAM: $Hex <- Crc error. (${line_num}th line \"$_\").\n";
+ exit(1);
+ }
+
+ my ($count, $addr, $type, $bytes) = unpack('CnCX4Cx3/a', $binrec);
+
+ my @codes = unpack('C*', $bytes);
+
+ Log(sprintf("$..(2) (\"$_\") count = $count, bytecount = $bytecount, addr = 0x%04X, type = $type", $addr), 7);
+
+ if ($type == INHX_EOF_REC)
+ {
+ last;
+ }
+ elsif ($type == INHX_EXT_LIN_ADDR_REC)
+ {
+ $addr_H = unpack('n', $bytes); # big-endian
+
+ Log(sprintf("$..(3) (\"$_\") addr_H = 0x%04X\n", $addr_H), 7);
+
+ $format = INHX32;
+ Log('format = INHX32', 7);
+ next;
+ }
+ elsif ($type != INHX_DATA_REC)
+ {
+ close(IN);
+ printf STDERR "$PROGRAM: $Hex <- Unknown type of record: 0x%02X (${line_num}th line \"$_\").\n", $type;
+ exit(1);
+ }
+
+ if ($bytecount == $count) # INHX32
+ {
+ if ($format == INHX8M)
+ {
+ close(IN);
+ print STDERR "$PROGRAM: $Hex <- Mixed format of file (${line_num}th line \"$_\").\n";
+ exit(1);
+ }
+
+ my $addr32 = ($addr_H << 16) | $addr;
+
+ map { store_code($_, \$addr32) } @codes;
+ }
+ elsif ($bytecount == ($count * BYTE_SIZE)) # INHX8M
+ {
+ if ($format == INHX32)
+ {
+ close(IN);
+ print STDERR "$PROGRAM: $Hex <- Mixed format of file (${line_num}th line \"$_\").\n";
+ exit(1);
+ }
+
+ map { store_code($_, \$addr) } @codes;
+ }
+ else
+ {
+ close(IN);
+ print STDERR "$PROGRAM: $Hex <- Wrong format of file (${line_num}th line \"$_\").\n";
+ exit(1);
+ }
+ } # while (<IN>)
+
+ close(IN);
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Determines that the $Address belongs to a constant.
+ #
+
+sub is_constant($)
+ {
+ my $Address = $_[0];
+
+ foreach (sort {$a <=> $b} keys(%const_areas_by_address))
+ {
+ return TRUE if ($_ <= $Address && $Address <= $const_areas_by_address{$_});
+ last if ($_ > $Address);
+ }
+
+ foreach (sort {$a <=> $b} keys(%const_blocks_by_address))
+ {
+ return TRUE if ($_ <= $Address && $Address <= $const_blocks_by_address{$_});
+ last if ($_ > $Address);
+ }
+
+ return FALSE;
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Determines that the $Address belongs to a empty area.
+ #
+
+sub is_empty($)
+ {
+ my $Address = $_[0];
+
+ foreach (sort {$a <=> $b} keys(%empty_blocks_by_address))
+ {
+ return TRUE if ($_ <= $Address && $Address <= $empty_blocks_by_address{$_});
+ last if ($_ > $Address);
+ }
+
+ return FALSE;
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Creates a const block.
+ #
+
+sub add_const_area($$)
+ {
+ $const_areas_by_address{$_[0]} = $_[1];
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Creates a new block, or modifies one.
+ #
+
+sub add_block($$$$$)
+ {
+ my ($Address, $Type, $Size, $LabelType, $LabelName) = @_;
+ my ($block, $label, $end);
+
+ $end = $Address + $Size - 1;
+
+ if (! defined($blocks_by_address{$Address}))
+ {
+ $label = {
+ TYPE => $LabelType,
+ NAME => $LabelName,
+ PRINTED => FALSE,
+ CALL_COUNT => 0,
+ JUMP_COUNT => 0
+ };
+
+ $blocks_by_address{$Address} = {
+ TYPE => $Type,
+ ADDR => $Address,
+ SIZE => $Size,
+ LABEL => $label,
+ REF_COUNT => 0
+ };
+
+ given ($Type)
+ {
+ when (BLOCK_INSTR)
+ {
+ if ($LabelType != BL_TYPE_NONE)
+ {
+ $labels_by_address{$Address} = $label;
+ $max_label_addr = $Address if ($max_label_addr < $Address);
+ }
+ }
+
+ when (BLOCK_RAM)
+ {
+ if ($LabelType != BL_TYPE_NONE)
+ {
+ $labels_by_address{$Address} = $label;
+ $max_label_addr = $Address if ($max_label_addr < $Address);
+ }
+
+ $ram_blocks_by_address{$Address} = $end if ($Size > 0);
+ }
+
+ when (BLOCK_CONST)
+ {
+ if ($LabelType != BL_TYPE_NONE)
+ {
+ $labels_by_address{$Address} = $label;
+ $max_label_addr = $Address if ($max_label_addr < $Address);
+ }
+
+ $const_blocks_by_address{$Address} = $end if ($Size > 0);
+ }
+
+ when (BLOCK_EMPTY)
+ {
+ # At empty area, can not be label.
+
+ $label->{TYPE} = BL_TYPE_NONE;
+ $label->{NAME} = '';
+ $empty_blocks_by_address{$Address} = $end if ($Size > 0);
+ }
+
+ default
+ {
+ printf STDERR "add_block(0x%04X): Unknown block type!\n", $Address;
+ exit(1);
+ }
+ } # given ($Type)
+ } # if (! defined($blocks_by_address{$Address}))
+ else
+ {
+ $block = $blocks_by_address{$Address};
+ $label = $block->{LABEL};
+ $block->{TYPE} = $Type;
+ $block->{SIZE} = $Size if ($Size > 0);
+ $label->{NAME} = $LabelName if ($label->{NAME} eq '' && $LabelName ne '');
+
+ given ($Type)
+ {
+ when (BLOCK_INSTR)
+ {
+ if ($LabelType != BL_TYPE_NONE)
+ {
+ $label->{TYPE} = $LabelType;
+ $labels_by_address{$Address} = $label;
+ $max_label_addr = $Address if ($max_label_addr < $Address);
+ }
+ }
+
+ when (BLOCK_RAM)
+ {
+ if ($LabelType != BL_TYPE_NONE)
+ {
+ $label->{TYPE} = $LabelType;
+ $labels_by_address{$Address} = $label;
+ $max_label_addr = $Address if ($max_label_addr < $Address);
+ }
+
+ $ram_blocks_by_address{$Address} = $end if ($Size > 0);
+ }
+
+ when (BLOCK_CONST)
+ {
+ if ($LabelType != BL_TYPE_NONE)
+ {
+ $label->{TYPE} = $LabelType;
+ $labels_by_address{$Address} = $label;
+ $max_label_addr = $Address if ($max_label_addr < $Address);
+ }
+
+ $const_blocks_by_address{$Address} = $end if ($Size > 0);
+ }
+
+ when (BLOCK_EMPTY)
+ {
+ # At empty area, can not be label.
+
+ $label->{TYPE} = BL_TYPE_NONE;
+ $label->{NAME} = '';
+ $empty_blocks_by_address{$Address} = $end if ($Size > 0);
+ }
+ } # given ($Type)
+ }
+
+ return $label;
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Store address entry of a procedure.
+ #
+
+sub add_func_label($$$)
+ {
+ my ($Address, $Name, $Map_mode) = @_;
+ my $label;
+
+ if ($Address < 0)
+ {
+ Log(sprintf("add_func_label(): This address (0x%04X) negative!", $Address), 2);
+ return;
+ }
+
+ if (! $Map_mode)
+ {
+ if (! defined($blocks_by_address{$Address}))
+ {
+ Log(sprintf("add_func_label(): This address (0x%04X) does not shows an instruction!", $Address), 2);
+ return;
+ }
+ }
+
+ if (is_constant($Address) || is_empty($Address))
+ {
+ Log(sprintf("add_func_label(): This address (0x%04X) outside the code area!", $Address), 2);
+ return;
+ }
+
+ $label = add_block($Address, BLOCK_INSTR, 0, BL_TYPE_SUB, $Name);
+
+ if (! $Map_mode)
+ {
+ ++$label->{CALL_COUNT};
+ ++$blocks_by_address{$Address}->{REF_COUNT};
+ }
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Store a label.
+ #
+
+sub add_jump_label($$$$$)
+ {
+ my ($TargetAddr, $Name, $Type, $SourceAddr, $Map_mode) = @_;
+ my ($label, $type);
+
+ if ($TargetAddr < 0)
+ {
+ Log(sprintf("add_jump_label(): This address (0x%04X) negative!", $TargetAddr), 2);
+ return;
+ }
+
+ if (! $Map_mode)
+ {
+ if (! defined($blocks_by_address{$TargetAddr}))
+ {
+ Log(sprintf("add_jump_label(): This address (0x%04X) does not shows an instruction!", $TargetAddr), 2);
+ return;
+ }
+ }
+
+ if (is_constant($TargetAddr) || is_empty($TargetAddr))
+ {
+ Log(sprintf("add_jump_label(): This address (0x%04X) outside the code area!", $TargetAddr), 2);
+ return;
+ }
+
+ if (defined($interrupts_by_address{$SourceAddr}))
+ {
+ $Type = BL_TYPE_SUB;
+ $Name = $interrupts_by_address{$SourceAddr} if ($Name eq '');
+ }
+
+ $label = add_block($TargetAddr, BLOCK_INSTR, 0, $Type, $Name);
+
+ if (! $Map_mode)
+ {
+ ++$label->{JUMP_COUNT};
+ ++$blocks_by_address{$TargetAddr}->{REF_COUNT};
+ }
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Store a variable name.
+ #
+
+sub add_ram($$$)
+ {
+ my ($Address, $Name, $Map_mode) = @_;
+
+ return if ($Address == EMPTY);
+
+ if ($Address < 0)
+ {
+ Log(sprintf("add_ram(): This address (0x%04X) negative!", $Address), 2);
+ return;
+ }
+
+ add_block($Address, BLOCK_RAM, 1, BL_TYPE_VARIABLE, $Name);
+
+ ++$blocks_by_address{$Address}->{REF_COUNT} if (! $Map_mode);
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Store a I/O port name.
+ #
+
+sub add_io($$$)
+ {
+ my ($Address, $Name, $Map_mode) = @_;
+ my $io;
+
+ return if ($Address == EMPTY);
+
+ if (! defined($io = $io_by_address{$Address}))
+ {
+ $io_by_address{$Address} = {
+ NAME => $Name,
+ REF_COUNT => ($Map_mode) ? 0 : 1
+ };
+ }
+ else
+ {
+ ++$io->{REF_COUNT} if (! $Map_mode);
+ }
+ }
+
+################################################################################
+################################################################################
+
+use constant MAP_NULL => 0;
+use constant MAP_BORDER => 1;
+use constant MAP_AREA => 2;
+use constant MAP_CODE => 3;
+use constant MAP_DATA => 4;
+
+ #
+ # If exists the map file, then extracts out of it the labels,
+ # variables and some segments.
+ #
+
+sub read_map_file()
+ {
+ my $state;
+
+ return if ($map_file eq '');
+
+ $state = MAP_NULL;
+
+ if (! open(MAP, '<', $map_file))
+ {
+ print STDERR "$PROGRAM : Could not open. -> \"$map_file\"\n";
+ exit(1);
+ }
+
+ while (<MAP>)
+ {
+ chomp;
+ s/\r$//o;
+
+ if ($state == MAP_NULL)
+ {
+ $state = MAP_BORDER if (/^Area\s+/io);
+ }
+ elsif ($state == MAP_BORDER)
+ {
+ $state = MAP_AREA if (/^-+\s+/o);
+ }
+ elsif ($state == MAP_AREA)
+ {
+ if (/^_CODE\s+/o)
+ {
+ $state = MAP_CODE;
+ }
+ elsif (/^_(DATA|INITIALIZED)\s+/o)
+ {
+ $state = MAP_DATA;
+ }
+ else
+ {
+ $state = MAP_NULL;
+ }
+ }
+ elsif ($state == MAP_CODE)
+ {
+ if (/^.ASxxxx Linker\s+/io)
+ {
+ $state = MAP_NULL;
+ }
+ elsif (/^\s+([[:xdigit:]]+)\s+(\S+)/o)
+ {
+ # 00000180 _main main
+ # 00000190 _main_end main
+ # 000001A2 _puts conio
+ # 000001B5 _puthex conio
+ # 00000201 _puthex8 conio
+
+ add_func_label(hex($1), $2, TRUE);
+ }
+ } # elsif ($state == MAP_CODE)
+ elsif ($state == MAP_DATA)
+ {
+ if (/^.ASxxxx Linker\s+/io)
+ {
+ $state = MAP_NULL;
+ }
+ elsif (/^\s*([[:xdigit:]]+)\s+(\S+)/o)
+ {
+ # 000006C6 _heap_top
+ # 000006C8 _last_error
+ # 000006C9 _old_isr
+
+ add_ram(hex($1), $2, TRUE);
+ }
+ } # elsif ($state == MAP_DATA)
+ } # while (<MAP>)
+
+ $map_readed = TRUE;
+ close(MAP);
+ }
+
+#-------------------------------------------------------------------------------
+
+use constant NAMES_NULL => 0;
+use constant NAMES_RAM => 1;
+use constant NAMES_IO => 2;
+use constant NAMES_ROM => 3;
+
+sub read_name_list()
+ {
+ my ($line, $addr, $name, $state);
+
+ return if ($name_list eq '');
+
+ if (! open(NAMES, '<', $name_list))
+ {
+ print STDERR "$PROGRAM : Could not open. -> \"$name_list\"\n";
+ exit(1);
+ }
+
+ $state = NAMES_NULL;
+
+ foreach (grep(! /^\s*$/o, <NAMES>))
+ {
+ chomp;
+ s/\r$//o;
+ s/^\s*|\s*$//go;
+
+ if (/^\[RAM\]$/io)
+ {
+ $state = NAMES_RAM;
+ next;
+ }
+ elsif (/^\[IO\]$/io)
+ {
+ $state = NAMES_IO;
+ next;
+ }
+ elsif (/^\[ROM\]$/io)
+ {
+ $state = NAMES_ROM;
+ next;
+ }
+
+ $line = $_;
+
+ given ($state)
+ {
+ when (NAMES_RAM)
+ {
+ if ($line =~ /^0x([[:xdigit:]]+)\s*:\s*(\S+)$/io)
+ {
+ add_ram(hex($1), $2, TRUE);
+ }
+ }
+
+ when (NAMES_IO)
+ {
+ if ($line =~ /^0x([[:xdigit:]]+)\s*:\s*(\S+)$/io)
+ {
+ add_io(hex($1), $2, TRUE);
+ }
+ }
+
+ when (NAMES_ROM)
+ {
+ if ($line =~ /^0x([[:xdigit:]]+)\s*:\s*(\S+)$/io)
+ {
+ add_jump_label(hex($1), $2, BL_TYPE_LABEL, EMPTY, TRUE);
+ }
+ }
+ } # given ($state)
+ } # foreach (grep(! /^\s*$/o, <NAMES>))
+
+ close(NAMES);
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # There are some variables that are multi-byte. However, only
+ # the LSB byte of having a name. This procedure gives a name
+ # for the higher-significant bytes.
+ #
+
+sub fix_multi_byte_variables()
+ {
+ my ($block, $prev_addr, $prev_name, $name, $i, $var_size);
+
+ $prev_addr = EMPTY;
+ $prev_name = '';
+ foreach (sort {$a <=> $b} keys(%blocks_by_address))
+ {
+ $block = $blocks_by_address{$_};
+ $name = $block->{LABEL}->{NAME};
+
+ if ($block->{TYPE} != BLOCK_RAM)
+ {
+ $prev_addr = EMPTY;
+ $prev_name = '';
+ next;
+ }
+
+ $ram_names_by_address{$_} = $name;
+
+ if ($name eq '')
+ {
+ $prev_addr = EMPTY;
+ $prev_name = '';
+ next;
+ }
+
+ if ($prev_addr != EMPTY)
+ {
+ $var_size = $_ - $prev_addr;
+
+ if ($var_size > 1)
+ {
+ # This is a multi-byte variable. Make the aliases.
+
+ for ($i = 1; $i < $var_size; ++$i)
+ {
+ $ram_names_by_address{$prev_addr + $i} = "($prev_name + $i)";
+ }
+
+ $blocks_by_address{$prev_addr}->{SIZE} = $var_size;
+ }
+ }
+
+ $prev_addr = $_;
+ $prev_name = $name;
+ } # foreach (sort {$a <=> $b} keys(%blocks_by_address))
+ }
+
+#-------------------------------------------------------------------------------
+
+sub fix_io_names()
+ {
+ my $i = 0;
+
+ foreach (sort {$a <=> $b} keys(%io_by_address))
+ {
+ next if ($io_by_address{$_}->{NAME} ne '');
+
+ $io_by_address{$_}->{NAME} = "io_$i";
+ ++$i;
+ }
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # If there is left in yet so label that has no name, this here get one.
+ #
+
+sub add_names_labels()
+ {
+ my ($addr, $label, $fidx, $lidx, $jtidx, $cidx, $type);
+
+ $fidx = 0;
+ $lidx = 0;
+ $jtidx = 0;
+ $cidx = 0;
+
+ for ($addr = 0; $addr <= $max_label_addr; ++$addr)
+ {
+ $label = $labels_by_address{$addr};
+
+ next if (! defined($label));
+
+ $type = $label->{TYPE};
+
+ next if ($type == BL_TYPE_NONE || (defined($label->{NAME}) && $label->{NAME} ne ''));
+
+ if ($type == BL_TYPE_SUB)
+ {
+ $label->{NAME} = sprintf("$label_names{$type}%03u", $fidx++);
+ }
+ elsif ($type == BL_TYPE_LABEL)
+ {
+ $label->{NAME} = sprintf("$label_names{$type}%03u", $lidx++);
+ }
+ elsif ($type == BL_TYPE_JTABLE)
+ {
+ $label->{NAME} = sprintf("$label_names{$type}%03u", $jtidx++);
+ }
+ elsif ($type == BL_TYPE_CONST)
+ {
+ $label->{NAME} = sprintf("$label_names{$type}%03u", $cidx++);
+ }
+ }
+ }
+
+################################################################################
+################################################################################
+
+ #
+ # Expand a relative offset value.
+ #
+
+sub expand_offset($)
+ {
+ my $Offset = $_[0];
+
+ return ($Offset & 0x80) ? -(($Offset ^ 0xFF) + 1) : $Offset;
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Finds address of branchs and procedures.
+ #
+
+sub label_finder($$)
+ {
+ my ($Address, $BlockRef) = @_;
+ my ($instr_size, $instr, $addr);
+
+ $instr_size = $BlockRef->{SIZE};
+ $instr = $rom[$Address];
+
+ if ($instr == INST_JP)
+ {
+ # JP addr16 11000011 aaaaaaaa aaaaaaaa a7-a0 a15-a8 absolute address
+
+ $addr = ($rom[$Address + 2] << 8) | $rom[$Address + 1];
+ add_jump_label($addr, '', BL_TYPE_LABEL, $Address, FALSE);
+ }
+ elsif (($instr & 0xC7) == INST_JP_CC)
+ {
+ # JP cc, addr16 11ccc010 aaaaaaaa aaaaaaaa a7-a0 a15-a8 absolute address
+
+ $addr = ($rom[$Address + 2] << 8) | $rom[$Address + 1];
+ add_jump_label($addr, '', BL_TYPE_LABEL, $Address, FALSE);
+ }
+ elsif ($instr == INST_JR)
+ {
+ # JR rel 00011000 eeeeeee relative address
+
+ $addr = $Address + $instr_size + expand_offset($rom[$Address + 1]);
+ add_jump_label($addr, '', BL_TYPE_LABEL, EMPTY, FALSE);
+ }
+ elsif (($instr & 0xE7) == INST_JR_CC)
+ {
+ # JR cc, rel 00100000 eeeeeee relative address
+
+ $addr = $Address + $instr_size + expand_offset($rom[$Address + 1]);
+ add_jump_label($addr, '', BL_TYPE_LABEL, EMPTY, FALSE);
+ }
+ elsif ($instr == INST_DJNZ)
+ {
+ # DJNZ rel 00010000 eeeeeee relative address
+
+ $addr = $Address + $instr_size + expand_offset($rom[$Address + 1]);
+ add_jump_label($addr, '', BL_TYPE_LABEL, EMPTY, FALSE);
+ }
+ elsif ($instr == INST_CALL)
+ {
+ # CALL addr16 11001101 aaaaaaaa aaaaaaaa a7-a0 a15-a8 absolute address
+
+ $addr = ($rom[$Address + 2] << 8) | $rom[$Address + 1];
+ add_func_label($addr, '', FALSE);
+ }
+ elsif (($instr & 0xC7) == INST_CALL_CC)
+ {
+ # CALL cc, addr16 11ccc100 aaaaaaaa aaaaaaaa a7-a0 a15-a8 absolute address
+
+ $addr = ($rom[$Address + 2] << 8) | $rom[$Address + 1];
+ add_func_label($addr, '', FALSE);
+ }
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # If exists a label name wich belong to the $Address, then returns it.
+ # Otherwise, returns the address.
+ #
+
+sub label_name($)
+ {
+ my $Address = $_[0];
+ my $label = $labels_by_address{$Address};
+
+ return ((defined($label) && $label->{NAME} ne '') ? $label->{NAME} : (sprintf '0x%04X', $Address));
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # If exists a I/O port name wich belong to the $Address, then returns it.
+ # Otherwise, returns the address.
+ #
+
+sub io_name($)
+ {
+ my $Address = $_[0];
+ my $io = $io_by_address{$Address};
+
+ return ((defined($io) && $io->{NAME} ne '') ? $io->{NAME} : (sprintf '0x%02X', $Address));
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # If exists a variable name wich belong to the $Address, then returns it.
+ # Otherwise, returns the address.
+ #
+
+sub reg_name($$)
+ {
+ my ($Address, $StrRef) = @_;
+ my ($ram, $str);
+
+ if (defined($ram = $ram_names_by_address{$Address}) && $ram ne '')
+ {
+ $str = sprintf "0x%04X", $Address;
+ ${$StrRef} = "[$str]";
+ $str = $ram;
+ }
+ else
+ {
+ $str = sprintf "0x%04X", $Address;
+ ${$StrRef} = "[$str]";
+ }
+
+ return $str;
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Auxiliary procedure of prints.
+ #
+
+sub print_3($$$)
+ {
+ my ($Instr, $Param, $Expl) = @_;
+
+ return if ($decoder_silent_level > SILENT0);
+
+ if ($no_explanations)
+ {
+ print(($Param ne '') ? "$Instr\t$Param\n" : "$Instr\n");
+ }
+ elsif ($Expl ne '')
+ {
+ print "$Instr\t" . align($Param, EXPL_ALIGN_SIZE) . "; $Expl\n";
+ }
+ else
+ {
+ print(($Param ne '') ? "$Instr\t$Param\n" : "$Instr\n");
+ }
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # If possible, returns the character.
+ #
+
+sub decode_char($)
+ {
+ my $Ch = $_[0];
+
+ if ($Ch >= ord(' ') && $Ch < 0x7F)
+ {
+ return sprintf " {'%c'}", $Ch;
+ }
+ elsif (defined($control_characters{$Ch}))
+ {
+ return " {'$control_characters{$Ch}'}";
+ }
+
+ return '';
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Determines direction of jump.
+ #
+
+sub jump_direction($)
+ {
+ my $TargetAddr = $_[0];
+ my ($str0, $str1, $str2);
+
+ $str0 = sprintf "0x%04X", $TargetAddr;
+
+ if ($dcd_address < $TargetAddr)
+ {
+ $str1 = '';
+ $str2 = ' (forward)';
+ }
+ elsif ($dcd_address == $TargetAddr)
+ {
+ $str1 = ' (endless loop)';
+ $str2 = '';
+ }
+ else
+ {
+ $str1 = '';
+ $str2 = ' (backward)';
+ }
+
+ return "$str2 hither: $str0$str1";
+ }
+
+#---------------------------------------------------------------------------------------------------
+#---------------------------------------------------------------------------------------------------
+#---------------------------------------------------------------------------------------------------
+#---------------------------------------------------------------------------------------------------
+
+my @core_registers8 =
+ (
+ {
+ NAME => 'B',
+ EXPL => 'B'
+ },
+ {
+ NAME => 'C',
+ EXPL => 'C'
+ },
+ {
+ NAME => 'D',
+ EXPL => 'D'
+ },
+ {
+ NAME => 'E',
+ EXPL => 'E'
+ },
+ {
+ NAME => 'H',
+ EXPL => 'H'
+ },
+ {
+ NAME => 'L',
+ EXPL => 'L'
+ },
+ {
+ NAME => '(HL)',
+ EXPL => '[HL]'
+ },
+ {
+ NAME => 'A',
+ EXPL => 'A'
+ }
+ );
+
+my @core_registers16a = ( 'BC', 'DE', 'HL', 'SP' );
+my @core_registers16b = ( 'BC', 'DE', 'HL', 'AF' );
+my @core_registers16c = ( 'BC', 'DE', 'IX', 'SP' );
+
+my @CB_shift_instr =
+ (
+ {
+ INSTR => 'rlc',
+ EXPL => 'CF <- %s[7..0] <- %s.7'
+ },
+ {
+ INSTR => 'rrc',
+ EXPL => '%s.0 -> %s[7..0] -> CF'
+ },
+ {
+ INSTR => 'rl',
+ EXPL => 'CF <- %s[7..0] <- CF'
+ },
+ {
+ INSTR => 'rr',
+ EXPL => 'CF -> %s[7..0] -> CF'
+ },
+ {
+ INSTR => 'sla',
+ EXPL => 'CF <- %s[7..0] <- 0'
+ },
+ {
+ INSTR => 'sra',
+ EXPL => '%s.7 -> %s[7..0] -> CF'
+ },
+ {
+ INSTR => 'sll',
+ EXPL => 'CF <- %s[7..0] <- 1'
+ },
+ {
+ INSTR => 'srl',
+ EXPL => '0 -> %s[7..0] -> CF'
+ }
+ );
+
+sub CB_prefix_decoder()
+ {
+ my ($str, $i_reg, $reg);
+
+ given ($dcd_instr_x)
+ {
+ when (0)
+ {
+# RLC r CB xx 11001011 00000rrr
+# RRC r CB xx 11001011 00001rrr
+# RL r CB xx 11001011 00010rrr
+# RR r CB xx 11001011 00011rrr
+# SLA r CB xx 11001011 00100rrr
+# SRA r CB xx 11001011 00101rrr
+# SLL r CB xx 11001011 00110rrr
+# SRL r CB xx 11001011 00111rrr
+# xxyyyzzz
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $i_shift = $CB_shift_instr[$dcd_instr_y];
+
+ $i_reg = $core_registers8[$dcd_instr_z];
+ $reg = $i_reg->{EXPL};
+ if ($dcd_instr_y == 0)
+ {
+ $str = sprintf $i_shift->{EXPL}, $reg, $reg;
+ }
+ else
+ {
+ $str = sprintf $i_shift->{EXPL}, $reg;
+ }
+ print_3($i_shift->{INSTR}, $i_reg->{NAME}, $str);
+ }
+ }
+
+ when (1)
+ {
+# BIT b, r CB 11001011 01bbbrrr
+# xxyyyzzz
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ $i_reg = $core_registers8[$dcd_instr_z];
+ print_3('bit', "$dcd_instr_y, $i_reg->{NAME}", "ZF = !$i_reg->{EXPL}.$dcd_instr_y");
+ }
+ }
+
+ when (2)
+ {
+# RES b, r CB 11001011 10bbbrrr
+# xxyyyzzz
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ $i_reg = $core_registers8[$dcd_instr_z];
+ print_3('res', "$dcd_instr_y, $i_reg->{NAME}", "$i_reg->{EXPL}.$dcd_instr_y = 0");
+ }
+ }
+
+ default
+ {
+# SET b, r CB 11001011 11bbbrrr
+# xxyyyzzz
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ $i_reg = $core_registers8[$dcd_instr_z];
+ print_3('set', "$dcd_instr_y, $i_reg->{NAME}", "$i_reg->{EXPL}.$dcd_instr_y = 1");
+ }
+ }
+ } # given ($dcd_instr_x)
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # $IndexReg: IX or IY
+ #
+
+sub DDFD_CB_prefix_decoder($)
+ {
+ my $IndexReg = $_[0];
+ my $offset = expand_offset($dcd_parm1);
+ my ($offs_str, $offs_expl, $i_reg);
+
+ if ($offset < 0)
+ {
+ $offs_str = "$offset($IndexReg)";
+ $offs_expl = "[$IndexReg$offset]";
+ }
+ else
+ {
+ $offs_str = "$offset($IndexReg)";
+ $offs_expl = "[${IndexReg}+$offset]";
+ }
+
+ given ($dcd_instr_x)
+ {
+=back
+ r: 000 B
+ 001 C
+ 010 D
+ 011 E
+ 100 H
+ 101 L
+ 110 (HL)
+ 111 A
+=cut
+ when (0)
+ {
+ if ($dcd_instr_z == 6)
+ {
+# RLC (IX+d) DD CB dd 06 11011101 11001011 dddddddd 00000110 d: two's complement number
+# RLC (IY+d) FD CB dd 06 11111101 11001011 dddddddd 00000110 d: two's complement number
+# RRC (IX+d) DD CB dd 0E 11011101 11001011 dddddddd 00001110 d: two's complement number
+# RRC (IY+d) FD CB dd 0E 11111101 11001011 dddddddd 00001110 d: two's complement number
+# RL (IX+d) DD CB dd 16 11011101 11001011 dddddddd 00010110 d: two's complement number
+# RL (IY+d) FD CB dd 16 11111101 11001011 dddddddd 00010110 d: two's complement number
+# RR (IX+d) DD CB dd 1E 11011101 11001011 dddddddd 00011110 d: two's complement number
+# RR (IY+d) FD CB dd 1E 11111101 11001011 dddddddd 00011110 d: two's complement number
+# SLA (IX+d) DD CB dd 26 11011101 11001011 dddddddd 00100110 d: two's complement number
+# SLA (IY+d) FD CB dd 26 11111101 11001011 dddddddd 00100110 d: two's complement number
+# SRA (IX+d) DD CB dd 2E 11011101 11001011 dddddddd 00101110 d: two's complement number
+# SRA (IY+d) FD CB dd 2E 11111101 11001011 dddddddd 00101110 d: two's complement number
+# SLL (IX+d) DD CB dd 36 11011101 11001011 dddddddd 00110110 d: two's complement number
+# SLL (IY+d) FD CB dd 36 11111101 11001011 dddddddd 00110110 d: two's complement number
+# SRL (IX+d) DD CB dd 3E 11011101 11001011 dddddddd 00111110 d: two's complement number
+# SRL (IY+d) FD CB dd 3E 11111101 11001011 dddddddd 00111110 d: two's complement number
+# xxyyyzzz
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $shift = $CB_shift_instr[$dcd_instr_y];
+ my $str = sprintf $shift->{EXPL}, $offs_expl, $offs_expl;
+
+ print_3($shift->{INSTR}, $offs_str, $str);
+ }
+ }
+ else
+ {
+# LD r, RLC(IX+d) DD CB dd 0x 11011101 11001011 dddddddd 00000rrr d: two's complement number
+# LD r, RLC(IY+d) FD CB dd 0x 11111101 11001011 dddddddd 00000rrr d: two's complement number
+# LD r, RRC(IX+d) DD CB dd 0x 11011101 11001011 dddddddd 00001rrr d: two's complement number
+# LD r, RRC(IY+d) FD CB dd 0x 11111101 11001011 dddddddd 00001rrr d: two's complement number
+# LD r, RL(IX+d) DD CB dd 1x 11011101 11001011 dddddddd 00010rrr d: two's complement number
+# LD r, RL(IY+d) FD CB dd 1x 11111101 11001011 dddddddd 00010rrr d: two's complement number
+# LD r, RR(IX+d) DD CB dd 1x 11011101 11001011 dddddddd 00011rrr d: two's complement number
+# LD r, RR(IY+d) FD CB dd 1x 11111101 11001011 dddddddd 00011rrr d: two's complement number
+# LD r, SLA(IX+d) DD CB dd 2x 11011101 11001011 dddddddd 00100rrr d: two's complement number
+# LD r, SLA(IY+d) FD CB dd 2x 11111101 11001011 dddddddd 00100rrr d: two's complement number
+# LD r, SRA(IX+d) DD CB dd 2x 11011101 11001011 dddddddd 00101rrr d: two's complement number
+# LD r, SRA(IY+d) FD CB dd 2x 11111101 11001011 dddddddd 00101rrr d: two's complement number
+# LD r, SLL(IX+d) DD CB dd 3x 11011101 11001011 dddddddd 00110rrr d: two's complement number
+# LD r, SLL(IY+d) FD CB dd 3x 11111101 11001011 dddddddd 00110rrr d: two's complement number
+# LD r, SRL(IX+d) DD CB dd 3x 11011101 11001011 dddddddd 00111rrr d: two's complement number
+# LD r, SRL(IY+d) FD CB dd 3x 11111101 11001011 dddddddd 00111rrr d: two's complement number
+# xxyyyzzz
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $shift = $CB_shift_instr[$dcd_instr_y];
+ my $str = sprintf $shift->{EXPL}, $offs_expl, $offs_expl;
+
+ $i_reg = $core_registers8[$dcd_instr_z];
+ print_3('ld', "$i_reg->{NAME}, $shift->{INSTR} $offs_str", "$i_reg->{EXPL} = $str");
+ }
+ }
+ } # $dcd_instr_x == 0
+
+ when (1)
+ {
+# BIT b, (IX+d) DD CB dd 4x 11011101 11001011 dddddddd 01bbbxxx d: two's complement number
+# BIT b, (IY+d) FD CB dd 4x 11111101 11001011 dddddddd 01bbbxxx d: two's complement number
+# xxyyyzzz
+
+ print_3('bit', "$dcd_instr_y, $offs_str", "ZF = !${offs_expl}.$dcd_instr_y");
+ } # $dcd_instr_x == 1
+
+ when (2)
+ {
+ if ($dcd_instr_z == 6)
+ {
+# RES b, (IX+d) DD CB dd xx 11011101 11001011 dddddddd 10bbb110 d: two's complement number
+# RES b, (IY+d) FD CB dd xx 11111101 11001011 dddddddd 10bbb110 d: two's complement number
+# xxyyyzzz
+
+ print_3('res', "$dcd_instr_y, $offs_str", "${offs_expl}.$dcd_instr_y = 0");
+ }
+ else
+ {
+# LD r, RES b, (IX+d) DD CB dd xx 11011101 11001011 dddddddd 10bbbrrr d: two's complement number
+# LD r, RES b, (IY+d) FD CB dd xx 11111101 11001011 dddddddd 10bbbrrr d: two's complement number
+# xxyyyzzz
+
+ $i_reg = $core_registers8[$dcd_instr_z];
+ print_3('ld', "$i_reg->{NAME}, res $dcd_instr_y, $offs_str", "${offs_expl}.$dcd_instr_y = 0; $i_reg->{NAME} = $offs_expl");
+ }
+ } # $dcd_instr_x == 2
+
+ default
+ {
+ if ($dcd_instr_z == 6)
+ {
+# SET b, (IX+d) DD CB dd xx 11011101 11001011 dddddddd 11bbb110 d: two's complement number
+# SET b, (IY+d) FD CB dd xx 11111101 11001011 dddddddd 11bbb110 d: two's complement number
+# xxyyyzzz
+
+ print_3('set', "$dcd_instr_y, $offs_str", "${offs_expl}.$dcd_instr_y = 1");
+ }
+ else
+ {
+# LD r, SET b, (IX+d) DD CB dd xx 11011101 11001011 dddddddd 11bbbrrr d: two's complement number
+# LD r, SET b, (IY+d) FD CB dd xx 11111101 11001011 dddddddd 11bbbrrr d: two's complement number
+# xxyyyzzz
+
+ $i_reg = $core_registers8[$dcd_instr_z];
+ print_3('ld', "$i_reg->{NAME}, set $dcd_instr_y, $offs_str", "${offs_expl}.$dcd_instr_y = 1; $i_reg->{NAME} = $offs_expl");
+ }
+ }
+ } # given ($dcd_instr_x)
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # $IndexReg: IX or IY
+ #
+
+my @DDFD_instr =
+ (
+ {
+ INSTR => 'add',
+ EXPL => 'A +='
+ },
+ {
+ INSTR => 'adc',
+ EXPL => 'A += CF +'
+ },
+ {
+ INSTR => 'sub',
+ EXPL => 'A -='
+ },
+ {
+ INSTR => 'sbc',
+ EXPL => 'A -= CF +'
+ },
+ {
+ INSTR => 'and',
+ EXPL => 'A &='
+ },
+ {
+ INSTR => 'xor',
+ EXPL => 'A ^='
+ },
+ {
+ INSTR => 'or',
+ EXPL => 'A |='
+ },
+ {
+ INSTR => 'cp',
+ EXPL => 'A ?='
+ }
+ );
+
+sub DDFD_prefix_decoder($)
+ {
+ my $IndexReg = $_[0];
+ my ($addr, $offset, $offs_str, $offs_expl, $str);
+
+ if ($dcd_parm0 == 0xCB)
+ {
+ instruction_take_to_pieces($dcd_parm2);
+ DDFD_CB_prefix_decoder($IndexReg);
+ }
+ else
+ {
+ instruction_take_to_pieces($dcd_parm0);
+
+ $offset = expand_offset($dcd_parm1);
+
+ if ($offset < 0)
+ {
+ $offs_str = "$offset($IndexReg)";
+ $offs_expl = "[$IndexReg$offset]";
+ }
+ else
+ {
+ $offs_str = "$offset($IndexReg)";
+ $offs_expl = "[${IndexReg}+$offset]";
+ }
+
+ given ($dcd_instr_x)
+ {
+ when (0)
+ {
+ if ($dcd_instr_q == 1 && $dcd_instr_z == 1)
+ {
+# ADD IX, rp DD 09 11011101 00rr1001
+# ADD IY, rp FD 09 11111101 00rr1001
+# xxppqzzz
+# rp: BC, DE, IX, SP
+
+ $str = $core_registers16c[$dcd_instr_p];
+ print_3('add', "$IndexReg, $str", "$IndexReg += $str");
+ }
+ elsif ($dcd_instr_y == 4)
+ {
+ given ($dcd_instr_z)
+ {
+ when (1)
+ {
+# LD IX, #nn DD 21 nn nn 11011101 00100001 a7-0 a15-8
+# LD IY, #nn FD 21 nn nn 11111101 00100001 a7-0 a15-8
+# xxyyyzzz
+
+ $str = sprintf '0x%04X', ($dcd_parm2 << 8) | $dcd_parm1;
+ print_3('ld', "$IndexReg, #$str", "$IndexReg = $str");
+ }
+
+ when (2)
+ {
+# LD (nn), IX DD 22 nn nn 11011101 00100010 a7-0 a15-8
+# LD (nn), IY FD 22 nn nn 11111101 00100010 a7-0 a15-8
+# xxyyyzzz
+
+ $addr = ($dcd_parm2 << 8) | $dcd_parm1;
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $name;
+
+ $str = reg_name($addr, \$name);
+ print_3('ld', "($str), $IndexReg", "$name = $IndexReg");
+ }
+ elsif ($decoder_silent_level == SILENT1)
+ {
+ add_ram($addr, '', FALSE);
+ }
+ }
+
+ when (3)
+ {
+# INC IX DD 23 11011101 00100011
+# INC IY FD 23 11111101 00100011
+# xxyyyzzz
+
+ print_3('inc', $IndexReg, "++$IndexReg");
+ }
+
+ when (4)
+ {
+# INC IXh DD 24 11011101 00100100
+# INC IYh FD 24 11111101 00100100
+# xxyyyzzz
+
+ print_3('inc', "${IndexReg}h", "++${IndexReg}.h");
+ }
+
+ when (5)
+ {
+# DEC IXh DD 25 11011101 00100101
+# DEC IYh FD 25 11111101 00100101
+# xxyyyzzz
+
+ print_3('dec', "${IndexReg}h", "--${IndexReg}.h");
+ }
+
+ when (6)
+ {
+# LD IXh, #n DD 26 nn 11011101 00100110 nnnnnnnn
+# LD IYh, #n FD 26 nn 11111101 00100110 nnnnnnnn
+# xxyyyzzz
+
+ my $char = decode_char($dcd_parm1);
+
+ $str = sprintf '0x%02X', $dcd_parm1;
+ print_3('ld', "${IndexReg}h, #$str", "${IndexReg}.h = $str$char");
+ }
+ } # given ($dcd_instr_z)
+ }
+ elsif ($dcd_instr_y == 5)
+ {
+ given ($dcd_instr_z)
+ {
+ when (2)
+ {
+# LD IX, (nn) DD 2A nn nn 11011101 00101010 a7-0 a15-8
+# LD IY, (nn) FD 2A nn nn 11111101 00101010 a7-0 a15-8
+# xxyyyzzz
+
+ $addr = ($dcd_parm2 << 8) | $dcd_parm1;
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $name;
+
+ $str = reg_name($addr, \$name);
+ print_3('ld', "$IndexReg, ($str)", "$IndexReg = $name");
+ }
+ elsif ($decoder_silent_level == SILENT1)
+ {
+ add_ram($addr, '', FALSE);
+ }
+ }
+
+ when (3)
+ {
+# DEC IX DD 2B 11011101 00101011
+# DEC IY FD 2B 11111101 00101011
+# xxyyyzzz
+
+ print_3('dec', $IndexReg, "--$IndexReg");
+ }
+
+ when (4)
+ {
+# INC IXl DD 2C 11011101 00101100
+# INC IYl FD 2C 11111101 00101100
+# xxyyyzzz
+
+ print_3('inc', "${IndexReg}l", "++${IndexReg}.l");
+ }
+
+ when (5)
+ {
+# DEC IXl DD 2D 11011101 00101101
+# DEC IYl FD 2D 11111101 00101101
+# xxyyyzzz
+
+ print_3('dec', "${IndexReg}l", "--${IndexReg}.l");
+ }
+
+ when (6)
+ {
+# LD IXl, #n DD 2E nn 11011101 00101110 nnnnnnnn
+# LD IYl, #n FD 2E nn 11111101 00101110 nnnnnnnn
+# xxyyyzzz
+
+ my $char = decode_char($dcd_parm1);
+
+ $str = sprintf '0x%02X', $dcd_parm1;
+ print_3('ld', "${IndexReg}l, #$str", "${IndexReg}.l = $str$char");
+ }
+ } # given ($dcd_instr_z)
+ }
+ elsif ($dcd_instr_y == 6)
+ {
+ given ($dcd_instr_z)
+ {
+ when (4)
+ {
+# INC (IX+d) DD 34 dd 11011101 00110100 dddddddd d: two's complement number
+# INC (IY+d) FD 34 dd 11111101 00110100 dddddddd d: two's complement number
+# xxyyyzzz
+
+ print_3('inc', $offs_str, "++$offs_expl");
+ }
+
+ when (5)
+ {
+# DEC (IX+d) DD 35 dd 11011101 00110101 dddddddd d: two's complement number
+# DEC (IY+d) FD 35 dd 11111101 00110101 dddddddd d: two's complement number
+# xxyyyzzz
+
+ print_3('dec', $offs_str, "--$offs_expl");
+ }
+
+ when (6)
+ {
+# LD (IX+d), #n DD 36 dd nn 11011101 00110110 dddddddd nnnnnnnn d: two's complement number
+# LD (IY+d), #n FD 36 dd nn 11111101 00110110 dddddddd nnnnnnnn d: two's complement number
+# xxyyyzzz
+
+ my $char = decode_char($dcd_parm2);
+
+ $str = sprintf '0x%02X', $dcd_parm2;
+ print_3('ld', "$offs_str, #$str", "$offs_expl = $str$char");
+ }
+ } # given ($dcd_instr_z)
+ }
+ } # $dcd_instr_x == 0
+
+ when (1)
+ {
+ given ($dcd_instr_y)
+ {
+ when ([0 .. 3])
+ {
+ given ($dcd_instr_z)
+ {
+ when (4)
+ {
+# LD r, IXh DD 44 11011101 010rr100
+# LD r, IYh FD 44 11111101 010rr100
+# xxyyyzzz
+# r: B, C, D, E
+
+ $str = $core_registers8[$dcd_instr_y]->{NAME};
+ print_3('ld', "$str, ${IndexReg}h", "$str = ${IndexReg}.h");
+ }
+
+ when (5)
+ {
+
+# LD r, IXl DD 45 11011101 010rr101
+# LD r, IYl FD 45 11111101 010rr101
+# xxyyyzzz
+# r: B, C, D, E
+
+ $str = $core_registers8[$dcd_instr_y]->{NAME};
+ print_3('ld', "$str, ${IndexReg}l", "$str = ${IndexReg}.l");
+ }
+
+ when (6)
+ {
+
+# LD r, (IX+d) DD 46 dd 11011101 010rr110 dddddddd d: two's complement number
+# LD r, (IY+d) FD 46 dd 11111101 010rr110 dddddddd d: two's complement number
+# xxyyyzzz
+# r: B, C, D, E
+
+ $str = $core_registers8[$dcd_instr_y]->{NAME};
+ print_3('ld', "$str, $offs_str", "$str = $offs_expl");
+ }
+ } # given ($dcd_instr_z)
+ } # when ([0 .. 3])
+
+ when ([4, 5])
+ {
+ my $r = ($dcd_instr_y == 4) ? 'h' : 'l';
+
+ given ($dcd_instr_z)
+ {
+ when ([0 .. 3])
+ {
+# LD IXh, B DD 60 11011101 011000rr
+# LD IYh, B FD 60 11111101 011000rr
+# LD IXh, C DD 61 11011101 01100001
+# LD IYh, C FD 61 11111101 01100001
+# LD IXh, D DD 62 11011101 01100010
+# LD IYh, D FD 62 11111101 01100010
+# LD IXh, E DD 63 11011101 01100011
+# LD IYh, E FD 63 11111101 01100011
+# xxyyyzzz
+# r: B, C, D, E
+
+ $str = $core_registers8[$dcd_instr_z]->{NAME};
+ print_3('ld', "$IndexReg$r, $str", "$IndexReg$r = $str");
+ }
+
+ when (4)
+ {
+# LD IXh, IXh DD 64 11011101 01100100
+# LD IYh, IYh FD 64 11111101 01100100
+# xxyyyzzz
+
+ print_3('ld', "$IndexReg$r, {IndexReg}h", "$IndexReg$r = {IndexReg}.h");
+ }
+
+ when (5)
+ {
+# LD IXh, IXl DD 65 11011101 01100101
+# LD IYh, IYl FD 65 11111101 01100101
+# xxyyyzzz
+
+ print_3('ld', "$IndexReg$r, {IndexReg}l", "$IndexReg$r = {IndexReg}.l");
+ }
+
+ when (6)
+ {
+# LD H, (IX+d) DD 66 dd 11011101 01100110 dddddddd d: two's complement number
+# LD H, (IY+d) FD 66 dd 11111101 01100110 dddddddd d: two's complement number
+# xxyyyzzz
+
+ $str = uc($r);
+ print_3('ld', "$str, $offs_str", "$str = $offs_expl");
+ }
+
+ when (7)
+ {
+# LD IXh, A DD 67 11011101 01100111
+# LD IYh, A FD 67 11111101 01100111
+# xxyyyzzz
+
+ print_3('ld', "$IndexReg$r, A", "$IndexReg$r = A");
+ }
+ } # given ($dcd_instr_z)
+ } # when ([4, 5])
+
+ when (6)
+ {
+# LD (IX+d), r DD 70 dd 11011101 01110rrr dddddddd d: two's complement number
+# LD (IY+d), r FD 70 dd 11111101 01110rrr dddddddd d: two's complement number
+# xxyyyzzz
+# r: B, C, D, E, H, L, -, A
+
+ $str = $core_registers8[$dcd_instr_z]->{NAME};
+ print_3('ld', "$offs_str, $str", "$offs_expl = $str");
+ }
+
+ default
+ {
+ given ($dcd_instr_z)
+ {
+ when (4)
+ {
+# LD A, IXh DD 7C 11011101 01111100
+# LD A, IYh FD 7C 11111101 01111100
+# xxyyyzzz
+
+ print_3('ld', "A, ${IndexReg}h", "A = ${IndexReg}.h");
+ }
+
+ when (5)
+ {
+# LD A, IXl DD 7D 11011101 01111101
+# LD A, IYl FD 7D 11111101 01111101
+# xxyyyzzz
+
+ print_3('ld', "A, ${IndexReg}l", "A = ${IndexReg}.l");
+ }
+
+ when (6)
+ {
+# LD A, (IX+d) DD 7E dd 11011101 01111110 dddddddd d: two's complement number
+# LD A, (IY+d) FD 7E dd 11111101 01111110 dddddddd d: two's complement number
+# xxyyyzzz
+
+ print_3('ld', "A, $offs_str", "A = $offs_expl");
+ }
+ } # given ($dcd_instr_z)
+ }
+ } # given ($dcd_instr_y)
+ } # $dcd_instr_x == 1
+
+ when (2)
+ {
+ given ($dcd_instr_z)
+ {
+ when (4)
+ {
+# ADD A, IXh DD 84 11011101 10000100
+# ADD A, IYh FD 84 11111101 10000100
+# ADC A, IXh DD 8C 11011101 10001100
+# ADC A, IYh FD 8C 11111101 10001100
+# SUB A, IXh DD 94 11011101 10010100
+# SUB A, IYh FD 94 11111101 10010100
+# SBC A, IXh DD 9C 11011101 10011100
+# SBC A, IYh FD 9C 11111101 10011100
+# AND A, IXh DD A4 11011101 10100100
+# AND A, IYh FD A4 11111101 10100100
+# XOR A, IXh DD AC 11011101 10101100
+# XOR A, IYh FD AC 11111101 10101100
+# OR A, IXh DD B4 11011101 10110100
+# OR A, IYh FD B4 11111101 10110100
+# CP A, IXh DD BC 11011101 10111100
+# CP A, IYh FD BC 11111101 10111100
+# xxyyyzzz
+
+ my $i_arith = $DDFD_instr[$dcd_instr_y];
+
+ print_3($i_arith->{INSTR}, "A, ${IndexReg}h", "$i_arith->{EXPL} ${IndexReg}.h");
+ }
+
+ when (5)
+ {
+# ADD A, IXl DD 85 11011101 10000101
+# ADD A, IYl FD 85 11111101 10000101
+# ADC A, IXl DD 8D 11011101 10001101
+# ADC A, IYl FD 8D 11111101 10001101
+# SUB A, IXl DD 95 11011101 10010101
+# SUB A, IYl FD 95 11111101 10010101
+# SBC A, IXl DD 9D 11011101 10011101
+# SBC A, IYl FD 9D 11111101 10011101
+# AND A, IXl DD A5 11011101 10100101
+# AND A, IYl FD A5 11111101 10100101
+# XOR A, IXl DD AD 11011101 10101101
+# XOR A, IYl FD AD 11111101 10101101
+# OR A, IXl DD B5 11011101 10110101
+# OR A, IYl FD B5 11111101 10110101
+# CP A, IXl DD BD 11011101 10111101
+# CP A, IYl FD BD 11111101 10111101
+# xxyyyzzz
+
+ my $i_arith = $DDFD_instr[$dcd_instr_y];
+
+ print_3($i_arith->{INSTR}, "A, ${IndexReg}l", "$i_arith->{EXPL} ${IndexReg}.l");
+ }
+
+ when (6)
+ {
+# ADD A, (IX+d) DD 86 dd 11011101 10000110 dddddddd d: two's complement number
+# ADD A, (IY+d) FD 86 dd 11111101 10000110 dddddddd d: two's complement number
+# ADC A, (IX+d) DD 8E dd 11011101 10001110 dddddddd d: two's complement number
+# ADC A, (IY+d) FD 8E dd 11111101 10001110 dddddddd d: two's complement number
+# SUB A, (IX+d) DD 96 dd 11011101 10010110 dddddddd d: two's complement number
+# SUB A, (IY+d) FD 96 dd 11111101 10010110 dddddddd d: two's complement number
+# SBC A, (IX+d) DD 9E dd 11011101 10011110 dddddddd d: two's complement number
+# SBC A, (IY+d) FD 9E dd 11111101 10011110 dddddddd d: two's complement number
+# AND A, (IX+d) DD A6 dd 11011101 10100110 dddddddd d: two's complement number
+# AND A, (IY+d) FD A6 dd 11111101 10100110 dddddddd d: two's complement number
+# XOR A, (IX+d) DD AE dd 11011101 10101110 dddddddd d: two's complement number
+# XOR A, (IY+d) FD AE dd 11111101 10101110 dddddddd d: two's complement number
+# OR A, (IX+d) DD B6 dd 11011101 10110110 dddddddd d: two's complement number
+# OR A, (IY+d) FD B6 dd 11111101 10110110 dddddddd d: two's complement number
+# CP A, (IX+d) DD BE dd 11011101 10111110 dddddddd d: two's complement number
+# CP A, (IY+d) FD BE dd 11111101 10111110 dddddddd d: two's complement number
+# xxyyyzzz
+
+ my $i_arith = $DDFD_instr[$dcd_instr_y];
+
+ print_3($i_arith->{INSTR}, "A, $offs_str", "$i_arith->{EXPL} $offs_expl");
+ }
+ } # given ($dcd_instr_z)
+ } # $dcd_instr_x == 2
+
+ default
+ {
+ given ($dcd_parm0)
+ {
+ when (0xE1)
+ {
+# POP IX DD E1 11011101 11100001
+# POP IY FD E1 11111101 11100001
+
+ print_3('pop', $IndexReg, "${IndexReg}.l = [SP++]; ${IndexReg}.h = [SP++]");
+ }
+
+ when (0xE3)
+ {
+# EX (SP), IX DD E3 11011101 11100011
+# EX (SP), IY FD E3 11111101 11100011
+
+ print_3('ex', "(SP), $IndexReg", "[SP] <-> ${IndexReg}.l; [SP+1] <-> ${IndexReg}.h");
+ }
+
+ when (0xE5)
+ {
+# PUSH IX DD E5 11011101 11100101
+# PUSH IY FD E5 11111101 11100101
+
+ print_3('push', $IndexReg, "[--SP] = ${IndexReg}.h; [--SP] = ${IndexReg}.l");
+ }
+
+ when (0xE9)
+ {
+# JP (IX) DD E9 11011101 11101001
+# JP (IY) FD E9 11111101 11101001
+
+ print_3('jp', "($IndexReg)", "Jumps hither: [$IndexReg]");
+ $prev_is_jump = TRUE;
+ }
+
+ when (0xF9)
+ {
+# LD SP, IX DD F9 11011101 11111001
+# LD SP, IY FD F9 11111101 11111001
+
+ print_3('ld', "SP, $IndexReg", "SP = $IndexReg");
+ }
+ } # given ($dcd_parm0)
+ } # $dcd_instr_x == 3
+ } # given ($dcd_instr_x)
+ }
+ }
+
+#-------------------------------------------------------------------------------
+
+my @block_instr =
+ (
+ [
+ {
+ INSTR => 'ldi',
+ EXPL => '[DE++] = [HL++]; --BC'
+ },
+ {
+ INSTR => 'cpi',
+ EXPL => 'A ?= [HL++]; --BC'
+ },
+ {
+ INSTR => 'ini',
+ EXPL => '[HL++] = In{C}; --B'
+ },
+ {
+ INSTR => 'outi',
+ EXPL => 'Out{C} = [HL++]; --B'
+ }
+ ],
+ [
+ {
+ INSTR => 'ldd',
+ EXPL => '[DE--] = [HL--]; --BC'
+ },
+ {
+ INSTR => 'cpd',
+ EXPL => 'A ?= [HL--]; --BC'
+ },
+ {
+ INSTR => 'ind',
+ EXPL => '[HL--] = In{C}; --B'
+ },
+ {
+ INSTR => 'outd',
+ EXPL => 'Out{C} = [HL--]; --B'
+ }
+ ],
+ [
+ {
+ INSTR => 'ldir',
+ EXPL => '[DE++] = [HL++]; --BC; Exit this loop, then BC == 0.'
+ },
+ {
+ INSTR => 'cpir',
+ EXPL => 'A ?= [HL++]; --BC; Exit this loop, then BC == 0 or A == [HL].'
+ },
+ {
+ INSTR => 'inir',
+ EXPL => '[HL++] = In{C}; --B; Exit this loop, then B == 0.'
+ },
+ {
+ INSTR => 'otir',
+ EXPL => 'Out{C} = [HL++]; --B; Exit this loop, then B == 0.'
+ }
+ ],
+ [
+ {
+ INSTR => 'lddr',
+ EXPL => '[DE--] = [HL--]; --BC; Exit this loop, then BC == 0.'
+ },
+ {
+ INSTR => 'cpdr',
+ EXPL => 'A ?= [HL--]; --BC; Exit this loop, then BC == 0 or A == [HL].'
+ },
+ {
+ INSTR => 'indr',
+ EXPL => '[HL--] = In{C}; --B; Exit this loop, then B == 0.'
+ },
+ {
+ INSTR => 'otdr',
+ EXPL => 'Out{C} = [HL--]; --B; Exit this loop, then B == 0.'
+ }
+ ]
+ );
+
+sub ED_prefix_decoder()
+ {
+ my ($addr, $str, $i_reg, $reg);
+
+ instruction_take_to_pieces($dcd_parm0);
+
+ if ($dcd_instr_x == 1)
+ {
+ given ($dcd_instr_z)
+ {
+ when (0)
+ {
+ if ($decoder_silent_level == SILENT0)
+ {
+ $i_reg = $core_registers8[$dcd_instr_z];
+
+ if ($dcd_instr_y == 6)
+ {
+# IN (C) ED 70 11101011 01110000
+# xxyyyzzz
+
+ print_3('in', '(C)', "$i_reg->{EXPL} = In{[C]}");
+ }
+ else
+ {
+# IN r, (C) ED xx 11101011 01rrr000
+# xxyyyzzz
+
+ print_3('in', "$i_reg->{NAME}, (C)", "$i_reg->{EXPL} = In{[C]}");
+ }
+ }
+ } # $dcd_instr_z == 0
+
+ when (1)
+ {
+ if ($decoder_silent_level == SILENT0)
+ {
+ $i_reg = $core_registers8[$dcd_instr_z];
+
+ if ($dcd_instr_y == 6)
+ {
+# OUT (C) ED 71 11101101 01110001
+# xxyyyzzz
+
+ print_3('out', '(C)', "Out{[C]} = $i_reg->{EXPL}");
+ }
+ else
+ {
+# OUT (C), r ED xx 11101101 01rrr001
+# xxyyyzzz
+
+ print_3('out', "(C), $i_reg->{NAME}", "Out{[C]} = $i_reg->{EXPL}");
+ }
+ }
+ } # $dcd_instr_z == 1
+
+ when (2)
+ {
+ if ($dcd_instr_q == 0)
+ {
+# SBC HL, pp ED x2 11101101 01pp0010
+# xxppqzzz
+
+ $str = $core_registers16a[$dcd_instr_p];
+ print_3('sbc', "HL, $str", "HL -= $str + CF");
+ }
+ else
+ {
+# ADC HL, pp ED xA 11101101 01pp1010
+# xxppqzzz
+
+ $str = $core_registers16a[$dcd_instr_p];
+ print_3('adc', "HL, $str", "HL += $str + CF");
+ }
+ } # $dcd_instr_z == 2
+
+ when (3)
+ {
+ $addr = ($dcd_parm2 << 8) | $dcd_parm1;
+
+ if ($dcd_instr_q == 0)
+ {
+# LD (nn), pp ED x3 aa aa 11101101 01pp0011 a7-0 a15-8
+# xxppqzzz
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $name;
+
+ $reg = $core_registers16a[$dcd_instr_p];
+ $str = reg_name($addr, \$name);
+ print_3('ld', "($str), $reg", "$name = $reg");
+ }
+ elsif ($decoder_silent_level == SILENT1)
+ {
+ add_ram($addr, '', FALSE);
+ }
+ }
+ else
+ {
+# LD pp, (nn) ED xB aa aa 11101101 01pp1011 a7-0 a15-8
+# xxppqzzz
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $name;
+
+ $reg = $core_registers16a[$dcd_instr_p];
+ $str = reg_name($addr, \$name);
+ print_3('ld', "$reg, ($str)", "$reg = $name");
+ }
+ elsif ($decoder_silent_level == SILENT1)
+ {
+ add_ram($addr, '', FALSE);
+ }
+ }
+ } # $dcd_instr_z == 3
+
+ when (4)
+ {
+# NEG ED xx 11101101 01xxx100
+# xxyyyzzz
+
+ print_3('neg', '', 'A = -A');
+ } # $dcd_instr_z == 4
+
+ when (5)
+ {
+ if ($dcd_instr_y == 1)
+ {
+# RETI ED 4D 11101101 01001101
+# xxyyyzzz
+
+ print_3('reti', '', 'PC.l = [SP++]; PC.h = [SP++]; End of maskable interrupt.');
+ $prev_is_jump = TRUE;
+ }
+ else
+ {
+# RETN ED xx 11101101 01xxx101
+# xxyyyzzz
+
+ print_3('retn', '', 'PC.l = [SP++]; PC.h = [SP++]; End of non-maskable interrupt.');
+ $prev_is_jump = TRUE;
+ }
+ } # $dcd_instr_z == 5
+
+ when (6)
+ {
+# IM n ED xx 11101101 01xxx110
+# xxyyyzzz
+# y: 0 - im 0
+# 1 - im 0
+# 2 - im 1
+# 3 - im 2
+# 4 - im 0
+# 5 - im 0
+# 6 - im 1
+# 7 - im 2
+
+ $dcd_instr_y &= 3;
+ --$dcd_instr_y if ($dcd_instr_y);
+
+ print_3('im', $dcd_instr_y, "Interrupt mode ${dcd_instr_y}.");
+ } # $dcd_instr_z == 6
+
+ when (7)
+ {
+ given ($dcd_instr_y)
+ {
+ when (0)
+ {
+# LD I, A ED 47 11101101 01000111
+# xxyyyzzz
+
+ print_3('ld', 'I, A', 'I = A');
+ }
+
+ when (1)
+ {
+# LD R, A ED 4F 11101101 01001111
+# xxyyyzzz
+
+ print_3('ld', 'R, A', 'R = A');
+ }
+
+ when (2)
+ {
+# LD A, I ED 57 11101101 01010111
+# xxyyyzzz
+
+ print_3('ld', 'A, I', 'A = I');
+ }
+
+ when (3)
+ {
+# LD A, R ED 5F 11101101 01011111
+# xxyyyzzz
+
+ print_3('ld', 'A, R', 'A = R');
+ }
+
+ when (4)
+ {
+# RRD ED 67 11101101 01100111
+# xxyyyzzz
+
+ print_3('rrd', '', 'A[3..0] -> [HL][7..4] -> [HL][3..0] -> A[3..0]');
+ }
+
+ when (5)
+ {
+# RLD ED 6F 11101101 01101111
+# xxyyyzzz
+
+ print_3('rld', '', 'A[3..0] <- [HL][7..4] <- [HL][3..0] <- A[3..0]');
+ }
+
+ default
+ {
+# NOP ED 77 11101101 01110111
+# NOP ED 7F 11101101 01111111
+# xxyyyzzz
+
+ print_3('nop', '', 'No operation.');
+ } # $dcd_instr_y == 6 || $dcd_instr_y == 7
+ } # given ($dcd_instr_y)
+ } # $dcd_instr_z == 7
+ } # given ($dcd_instr_z)
+ } # if ($dcd_instr_x == 1)
+ elsif ($dcd_instr_x == 2 && $dcd_instr_y >= 4 && $dcd_instr_z <= 3)
+ {
+# LDI ED A0 11101101 10100000
+# CPI ED A1 11101101 10100001
+# INI ED A2 11101101 10100010
+# OUTI ED A3 11101101 10100011
+# xxyyyzzz
+
+# LDD ED A8 11101101 10101000
+# CPD ED A9 11101101 10101001
+# IND ED AA 11101101 10101010
+# OUTD ED AB 11101101 10101011
+# xxyyyzzz
+
+# LDIR ED B0 11101101 10110000
+# CPIR ED B1 11101101 10110001
+# INIR ED B2 11101101 10110010
+# OTIR ED B3 11101101 10110011
+# xxyyyzzz
+
+# LDDR ED B8 11101101 10111000
+# CPDR ED B9 11101101 10111001
+# INDR ED BA 11101101 10111010
+# OTDR ED BB 11101101 10111011
+# xxyyyzzz
+
+ my $i_block = $block_instr[$dcd_instr_y - 4][$dcd_instr_z];
+
+ print_3($i_block->{INSTR}, '', $i_block->{EXPL});
+ }
+ else
+ {
+ print_3('invalid instruction', '', '');
+ }
+ }
+
+#-------------------------------------------------------------------------------
+
+sub instruction_take_to_pieces($)
+ {
+ my $Instruction = $_[0];
+
+ $dcd_instr_x = ($Instruction >> 6) & 3;
+ $dcd_instr_y = ($Instruction >> 3) & 7;
+ $dcd_instr_z = $Instruction & 7;
+ $dcd_instr_p = ($Instruction >> 4) & 3;
+ $dcd_instr_q = ($Instruction >> 3) & 1;
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Decodes the $BlockRef.
+ #
+
+my @shift_instr =
+ (
+ {
+ INSTR => 'rlca',
+ EXPL => 'CF <- A[7..0] <- A.7'
+ },
+ {
+ INSTR => 'rrca',
+ EXPL => 'A.0 -> A[7..0] -> CF'
+ },
+ {
+ INSTR => 'rla',
+ EXPL => 'CF <- A[7..0] <- CF'
+ },
+ {
+ INSTR => 'rra',
+ EXPL => 'CF -> A[7..0] -> CF'
+ },
+ {
+ INSTR => 'daa',
+ EXPL => 'Conditionally decimal adjusts the Accumulator.'
+ },
+ {
+ INSTR => 'cpl',
+ EXPL => 'A = ~A'
+ },
+ {
+ INSTR => 'scf',
+ EXPL => 'CF = 1'
+ },
+ {
+ INSTR => 'ccf',
+ EXPL => 'CF = 0'
+ }
+ );
+
+my @conditions =
+ (
+ {
+ COND => 'NZ',
+ EXPL => 'ZF == 0'
+ },
+ {
+ COND => 'Z',
+ EXPL => 'ZF == 1'
+ },
+ {
+ COND => 'NC',
+ EXPL => 'CF == 0'
+ },
+ {
+ COND => 'C',
+ EXPL => 'CF == 1'
+ },
+ {
+ COND => 'PO',
+ EXPL => 'PF == 0'
+ },
+ {
+ COND => 'PE',
+ EXPL => 'PF == 1'
+ },
+ {
+ COND => 'P',
+ EXPL => 'SF == 0'
+ },
+ {
+ COND => 'M',
+ EXPL => 'SF == 1'
+ }
+ );
+
+sub instruction_decoder($$)
+ {
+ my ($Address, $BlockRef) = @_;
+ my ($addr, $label, $invalid, $str);
+
+ $dcd_address = $Address;
+ $dcd_instr_size = $BlockRef->{SIZE};
+ $dcd_instr = $rom[$dcd_address];
+ $label = $BlockRef->{LABEL};
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ printf("0x%04X: %02X", $dcd_address, $dcd_instr) if (! $gen_assembly_code);
+ }
+
+ $invalid = FALSE;
+
+ if ($dcd_instr_size == 1)
+ {
+ if ($decoder_silent_level == SILENT0)
+ {
+ print(($gen_assembly_code) ? "\t" : "\t\t");
+ }
+ }
+ elsif ($dcd_instr_size == 2)
+ {
+ $dcd_parm0 = $rom[$dcd_address + 1];
+ $invalid = TRUE if ($dcd_parm0 == EMPTY);
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ if ($gen_assembly_code)
+ {
+ print "\t";
+ }
+ else
+ {
+ printf " %02X\t\t", $dcd_parm0;
+ }
+ }
+ }
+ elsif ($dcd_instr_size == 3)
+ {
+ $dcd_parm0 = $rom[$dcd_address + 1];
+ $dcd_parm1 = $rom[$dcd_address + 2];
+ $invalid = TRUE if ($dcd_parm0 == EMPTY || $dcd_parm1 == EMPTY);
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ if ($gen_assembly_code)
+ {
+ print "\t";
+ }
+ else
+ {
+ printf " %02X %02X\t", $dcd_parm0, $dcd_parm1;
+ }
+ }
+ }
+ elsif ($dcd_instr_size == 4)
+ {
+ $dcd_parm0 = $rom[$dcd_address + 1];
+ $dcd_parm1 = $rom[$dcd_address + 2];
+ $dcd_parm2 = $rom[$dcd_address + 3];
+ $invalid = TRUE if ($dcd_parm0 == EMPTY || $dcd_parm1 == EMPTY || $dcd_parm2 == EMPTY);
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ if ($gen_assembly_code)
+ {
+ print "\t";
+ }
+ else
+ {
+ printf " %02X %02X %02X\t", $dcd_parm0, $dcd_parm1, $dcd_parm2;
+ }
+ }
+ }
+ else
+ {
+ printf STDERR "Internal error: The size of instruction (addr:0x%04X) is zero!", $dcd_address;
+ exit(1);
+ }
+
+ #
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | | | | | |
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | . | . | . | . | . | . | . | . |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # | | | |
+ # +---\ /---+ +\ /+
+ # p q
+ #
+
+ $prev_is_jump = FALSE;
+
+ instruction_take_to_pieces($dcd_instr);
+
+ if ($dcd_instr_x == 0)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 0 | 0 | . | . | . | . | . | . |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # +---\ /---+ +\ /+
+ # p q
+
+ given ($dcd_instr_z)
+ {
+ when (0)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 0 | 0 | . | . | . | 0 | 0 | 0 |
+ # +-----------------------------------------------+
+
+ given ($dcd_instr_y)
+ {
+ when (0)
+ {
+# NOP 00 00000000
+# xxyyyzzz
+
+ print_3('nop', '', 'No operation.');
+ }
+
+ when (1)
+ {
+# EX AF, AF' 08 00001000
+# xxyyyzzz
+
+ print_3('ex', "AF, AF'", "AF <-> AF'");
+ }
+
+ when (2)
+ {
+# DJNZ e 10 00010000 eeeeeeee e: two's complement number
+# xxyyyzzz
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $addr = $dcd_address + 2 + expand_offset($dcd_parm0);
+ my $target;
+
+ $str = label_name($addr);
+ $target = jump_direction($addr);
+ print_3('djnz', $str, "If (--B != 0) jumps$target");
+ $prev_is_jump = TRUE;
+ }
+ }
+
+ when (3)
+ {
+# JR e 18 00011000 eeeeeeee e: two's complement number
+# xxyyyzzz
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $addr = $dcd_address + 2 + expand_offset($dcd_parm0);
+ my $target;
+
+ $str = label_name($addr);
+ $target = jump_direction($addr);
+ print_3('jr', $str, "Jumps$target");
+ $prev_is_jump = TRUE;
+ }
+ }
+
+ default
+ {
+ # 4-7
+# JR cc, e xx 00ccc000 eeeeeeee e: two's complement number
+# xxyyyzzz
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $addr = $dcd_address + 2 + expand_offset($dcd_parm0);
+ my $cond = $conditions[$dcd_instr_y - 4];
+ my $target;
+
+ $str = label_name($addr);
+ $target = jump_direction($addr);
+ print_3('jr', "$cond->{COND}, $str", "Jumps if ($cond->{EXPL})$target");
+ $prev_is_jump = TRUE;
+ }
+ }
+ } # given ($dcd_instr_y)
+ } # $dcd_instr_z == 0
+
+ when (1)
+ {
+ # x z
+ # +---/ \---+ +------/ \------+
+ # | 7 6 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 0 | 0 | . | . | . | 0 | 0 | 1 |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # +---\ /---+ +\ /+
+ # p q
+
+ if ($dcd_instr_q == 0)
+ {
+# LD rp, #nn x1 00rr0001 nnnnnnnn nnnnnnnn
+# xxppqzzz
+# rp: BC, DE, HL, SP
+
+ my $r16 = $core_registers16a[$dcd_instr_p];
+
+ $str = sprintf '0x%04X', ($dcd_parm1 << 8) | $dcd_parm0;
+ print_3('ld', "$r16, #$str", "$r16 = $str");
+ }
+ else
+ {
+# ADD HL, rp x9 00rr1001
+# xxppqzzz
+# rp: BC, DE, HL, SP
+
+ $str = $core_registers16a[$dcd_instr_p];
+ print_3('add', "HL, $str", "HL += $str");
+ }
+ } # $dcd_instr_z == 1
+
+ when (2)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 0 | 0 | . | . | . | 0 | 1 | 0 |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # +---\ /---+ +\ /+
+ # p q
+
+ if ($dcd_instr_q == 0)
+ {
+ # x z
+ # +---/ \---+ +------/ \------+
+ # | 7 6 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 0 | 0 | . | . | 0 | 0 | 1 | 0 |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # +---\ /---+ +\ /+
+ # p q
+
+ given ($dcd_instr_p)
+ {
+ when (0)
+ {
+# LD (BC), A 02 00000010
+# xxppqzzz
+
+ print_3('ld', '(BC), A', '[BC] = A');
+ } # $dcd_instr_p == 0
+
+ when (1)
+ {
+# LD (DE), A 12 00010010
+# xxppqzzz
+
+ print_3('ld', '(DE), A', '[DE] = A');
+ } # $dcd_instr_p == 1
+
+ when (2)
+ {
+# LD (nn), HL 22 00100010 nnnnnnnn nnnnnnnn
+# xxppqzzz
+
+ $addr = ($dcd_parm1 << 8) | $dcd_parm0;
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $name;
+
+ $str = reg_name($addr, \$name);
+ print_3('ld', "($str), HL", "$name = HL");
+ }
+ elsif ($decoder_silent_level == SILENT1)
+ {
+ add_ram($addr, '', FALSE);
+ }
+ } # $dcd_instr_p == 2
+
+ when (3)
+ {
+# LD (nn), A 32 00110010 nnnnnnnn nnnnnnnn
+# xxppqzzz
+
+ $addr = ($dcd_parm1 << 8) | $dcd_parm0;
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $name;
+
+ $str = reg_name($addr, \$name);
+ print_3('ld', "($str), A", "$name = A");
+ }
+ elsif ($decoder_silent_level == SILENT1)
+ {
+ add_ram($addr, '', FALSE);
+ }
+ } # $dcd_instr_p == 3
+ } # given ($dcd_instr_p)
+ } # if ($dcd_instr_q == 0)
+ else
+ {
+ # x z
+ # +---/ \---+ +------/ \------+
+ # | 7 6 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 0 | 0 | . | . | 1 | 0 | 1 | 0 |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # +---\ /---+ +\ /+
+ # p q
+
+ given ($dcd_instr_p)
+ {
+ when (0)
+ {
+# LD A, (BC) 0A 00001010
+# xxppqzzz
+
+ print_3('ld', 'A, (BC)', 'A = [BC]');
+ } # $dcd_instr_p == 0
+
+ when (1)
+ {
+# LD A, (DE) 1A 00011010
+# xxppqzzz
+
+ print_3('ld', 'A, (DE)', 'A = [DE]');
+ } # $dcd_instr_p == 1
+
+ when (2)
+ {
+# LD HL, (nn) 2A 00101010 nnnnnnnn nnnnnnnn
+# xxppqzzz
+
+ $addr = ($dcd_parm1 << 8) | $dcd_parm0;
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $name;
+
+ $str = reg_name($addr, \$name);
+ print_3('ld', "HL, ($str)", "HL = $name");
+ }
+ elsif ($decoder_silent_level == SILENT1)
+ {
+ add_ram($addr, '', FALSE);
+ }
+ } # $dcd_instr_p == 2
+
+ when (3)
+ {
+# LD A, (nn) 3A 00111010 nnnnnnnn nnnnnnnn
+# xxppqzzz
+
+ $addr = ($dcd_parm1 << 8) | $dcd_parm0;
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $name;
+
+ $str = reg_name($addr, \$name);
+ print_3('ld', "A, ($str)", "A = $name");
+ }
+ elsif ($decoder_silent_level == SILENT1)
+ {
+ add_ram($addr, '', FALSE);
+ }
+ } # $dcd_instr_p == 3
+ } # given ($dcd_instr_p)
+ }
+ } # $dcd_instr_z == 2
+
+ when (3)
+ {
+ # x z
+ # +---/ \---+ +------/ \------+
+ # | 7 6 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 0 | 0 | . | . | . | 0 | 1 | 1 |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # +---\ /---+ +\ /+
+ # p q
+
+ if ($dcd_instr_q == 0)
+ {
+# INC rp x3 00rr0011
+# xxppqzzz
+# rp: BC, DE, HL, SP
+
+ $str = $core_registers16a[$dcd_instr_p];
+ print_3('inc', $str, "++$str");
+ }
+ else
+ {
+# DEC rp x3 00rr1011
+# xxppqzzz
+# rp: BC, DE, HL, SP
+
+ $str = $core_registers16a[$dcd_instr_p];
+ print_3('dec', $str, "--$str");
+ }
+ } # $dcd_instr_z == 3
+
+ when (4)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 0 | 0 | . | . | . | 1 | 0 | 0 |
+ # +-----------------------------------------------+
+
+# INC r xx 00rrr100
+# xxyyyzzz
+# r: B, C, D, E, H, L, (HL), A
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $i_reg = $core_registers8[$dcd_instr_y];
+
+ print_3('inc', $i_reg->{NAME}, "++$i_reg->{EXPL}");
+ }
+ } # $dcd_instr_z == 4
+
+ when (5)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 0 | 0 | . | . | . | 1 | 0 | 1 |
+ # +-----------------------------------------------+
+
+# DEC r xx 00rrr101
+# xxyyyzzz
+# r: B, C, D, E, H, L, (HL), A
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $i_reg = $core_registers8[$dcd_instr_y];
+
+ print_3('dec', $i_reg->{NAME}, "--$i_reg->{EXPL}");
+ }
+ } # $dcd_instr_z == 5
+
+ when (6)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 0 | 0 | . | . | . | 1 | 1 | 0 |
+ # +-----------------------------------------------+
+
+# LD r, #n 00rrr110 nnnnnnnn
+# xxyyyzzz
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $i_reg = $core_registers8[$dcd_instr_y];
+ my $char = decode_char($dcd_parm0);
+
+ $str = sprintf '0x%02X', $dcd_parm0;
+ print_3('ld', "$i_reg->{NAME}, #$str", "$i_reg->{EXPL} = $str$char");
+ }
+ } # $dcd_instr_z == 6
+
+ when (7)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 0 | 0 | . | . | . | 1 | 1 | 1 |
+ # +-----------------------------------------------+
+
+# RLCA 07 00000111
+# RRCA 0F 00001111
+# RLA 17 00010111
+# RRA 1F 00011111
+# DAA 27 00100111
+# CPL 2F 00101111
+# SCF 37 00110111
+# CCF 3F 00111111
+# xxyyyzzz
+
+ my $s_instr = $shift_instr[$dcd_instr_y];
+
+ print_3($s_instr->{INSTR}, '', $s_instr->{EXPL});
+ } # $dcd_instr_z == 7
+ } # given ($dcd_instr_z)
+ } # if ($dcd_instr_x == 0)
+ elsif ($dcd_instr_x == 1)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 0 | 1 | . | . | . | . | . | . |
+ # +-----------------------------------------------+
+
+ if ($dcd_instr_y == 6)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 0 | 1 | 1 | 1 | 0 | . | . | . |
+ # +-----------------------------------------------+
+
+# HALT 76 01110110
+# xxyyyzzz
+
+ print_3('halt', '', 'Suspends CPU.');
+ }
+ else
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 0 | 1 | . | . | . | . | . | . |
+ # +-----------------------------------------------+
+
+# LD r, r' xx 01dddsss
+# xxyyyzzz
+# r: B, C, D, E, H, L, (HL), A
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $i_rega = $core_registers8[$dcd_instr_y];
+ my $i_regb = $core_registers8[$dcd_instr_z];
+
+ print_3('ld', "$i_rega->{NAME}, $i_regb->{NAME}", "$i_rega->{EXPL} = $i_regb->{EXPL}");
+ }
+ }
+ } # elsif ($dcd_instr_x == 1)
+ elsif ($dcd_instr_x == 2)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 0 | . | . | . | . | . | . |
+ # +-----------------------------------------------+
+
+# ADD A, r 8x 10000rrr
+# ADC A, r 8x 10001rrr
+# SUB A, r 9x 10010rrr
+# SBC A, r 9x 10011rrr
+# AND A, r Ax 10100rrr
+# XOR A, r Ax 10101rrr
+# OR A, r Bx 10110rrr
+# CP A, r Bx 10111rrr
+# xxyyyzzz
+# r: B, C, D, E, H, L, (HL), A
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $i_arith = $DDFD_instr[$dcd_instr_y];
+ my $i_reg = $core_registers8[$dcd_instr_z];
+ my $str0 = ($dcd_instr_z == 7) ? ' (A = 0)' : '';
+
+ print_3($i_arith->{INSTR}, "A, $i_reg->{NAME}", "$i_arith->{EXPL} $i_reg->{EXPL}$str0");
+ }
+ }
+ else # $dcd_instr_x == 3
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 1 | . | . | . | . | . | . |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # +---\ /---+ +\ /+
+ # p q
+
+ given ($dcd_instr_z)
+ {
+ when (0)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 1 | . | . | . | 0 | 0 | 0 |
+ # +-----------------------------------------------+
+
+# RET cc xx 11ccc000
+# xxyyyzzz
+# cc: NZ, Z, NC, C, PO, PE, P, M
+
+ my $cond = $conditions[$dcd_instr_y];
+
+ print_3('ret', $cond->{COND}, "If ($cond->{EXPL}) PC.l = [SP++]; PC.h = [SP++]");
+ $prev_is_jump = TRUE;
+ }
+
+ when (1)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 1 | . | . | . | 0 | 0 | 1 |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # +---\ /---+ +\ /+
+ # p q
+
+ if ($dcd_instr_q == 0)
+ {
+ # x z
+ # +---/ \---+ +------/ \------+
+ # | 7 6 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 1 | . | . | 0 | 0 | 0 | 1 |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # +---\ /---+ +\ /+
+ # p q
+
+# POP rp xx 11rr0001
+# xxppqzzz
+# rp: BC, DE, HL, AF
+
+ given ($dcd_instr_p)
+ {
+ when (0) { $str = 'C = [SP++]; B = [SP++]'; }
+ when (1) { $str = 'E = [SP++]; D = [SP++]'; }
+ when (2) { $str = 'L = [SP++]; H = [SP++]'; }
+ when (3) { $str = 'F = [SP++]; A = [SP++]'; }
+ }
+
+ print_3('pop', $core_registers16b[$dcd_instr_p], $str);
+ }
+ else
+ {
+ # x z
+ # +---/ \---+ +------/ \------+
+ # | 7 6 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 1 | . | . | 1 | 0 | 0 | 1 |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # +---\ /---+ +\ /+
+ # p q
+
+ given ($dcd_instr_p)
+ {
+ when (0)
+ {
+# RET C9 11001001
+# xxppqzzz
+
+ print_3('ret', '', 'PC.l = [SP++]; PC.h = [SP++]');
+ $prev_is_jump = TRUE;
+ }
+
+ when (1)
+ {
+# EXX D9 11011001
+# xxppqzzz
+
+ print_3('exx', '', "BC <-> BC'; DE <-> DE'; HL <-> HL'");
+ }
+
+ when (2)
+ {
+# JP (HL) E9 11101001
+# xxppqzzz
+
+ print_3('jp', '(HL)', 'Jumps to value of HL.');
+ $prev_is_jump = TRUE;
+ }
+
+ when (3)
+ {
+# LD SP, HL F9 11111001
+# xxppqzzz
+
+ print_3('ld', 'SP, HL', 'SP = HL');
+ }
+ } # given ($dcd_instr_p)
+ }
+ } # $dcd_instr_z == 1
+
+ when (2)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 1 | . | . | . | 0 | 1 | 0 |
+ # +-----------------------------------------------+
+
+# JP cc, nn xx nn nn 11ccc010 a7-0 a15-8
+# xxyyyzzz
+# cc: NZ, Z, NC, C, PO, PE, P, M
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $addr = ($dcd_parm1 << 8) | $dcd_parm0;
+ my $cond = $conditions[$dcd_instr_y];
+ my $target;
+
+ $str = label_name($addr);
+ $target = jump_direction($addr);
+ print_3('jp', "$cond->{COND}, $str", "Jumps if ($cond->{EXPL})$target");
+ $prev_is_jump = TRUE;
+ }
+ }
+
+ when (3)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 1 | . | . | . | 0 | 1 | 1 |
+ # +-----------------------------------------------+
+
+ given ($dcd_instr_y)
+ {
+ when (0)
+ {
+# JP nn 11000011 a7-0 a15-8
+# xxyyyzzz
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $addr = ($dcd_parm1 << 8) | $dcd_parm0;
+ my $target;
+
+ $str = label_name($addr);
+ $target = jump_direction($addr);
+ print_3('jp', $str, "Jumps$target");
+ $prev_is_jump = TRUE;
+ }
+ }
+
+ when (1)
+ {
+ instruction_take_to_pieces($dcd_parm0);
+ CB_prefix_decoder();
+ }
+
+ when (2)
+ {
+# OUT (n), A D3 11010011 nnnnnnnn
+# xxyyyzzz
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $io = sprintf '0x%02X', $dcd_parm0;
+
+ if ($gen_assembly_code)
+ {
+ print_3('out', "($io), A", "Out{$io} = A");
+ }
+ else
+ {
+ $str = io_name($dcd_parm0);
+ print_3('out', "($str), A", "Out{$io} = A");
+ }
+ }
+ elsif ($decoder_silent_level == SILENT1)
+ {
+ add_io($dcd_parm0, '', FALSE);
+ }
+ }
+
+ when (3)
+ {
+# IN A, (n) DB 11011011 nnnnnnnn
+# xxyyyzzz
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $io = sprintf '0x%02X', $dcd_parm0;
+
+ if ($gen_assembly_code)
+ {
+ print_3('in', "A, ($io)", "A = In{$io}");
+ }
+ else
+ {
+ $str = io_name($dcd_parm0);
+ print_3('in', "A, ($str)", "A = In{$io}");
+ }
+ }
+ elsif ($decoder_silent_level == SILENT1)
+ {
+ add_io($dcd_parm0, '', FALSE);
+ }
+ }
+
+ when (4)
+ {
+# EX (SP), HL E3 11100011
+# xxyyyzzz
+
+ print_3('ex', '(SP), HL', "[SP] <-> L; [SP+1] <-> H");
+ }
+
+ when (5)
+ {
+# EX DE, HL EB 11101011
+# xxyyyzzz
+
+ print_3('ex', 'DE, HL', "E <-> L; D <-> H");
+ }
+
+ when (6)
+ {
+# DI F3 11110011
+# xxyyyzzz
+
+ print_3('di', '', 'Disable interrupts.');
+ }
+
+ when (7)
+ {
+# EI FB 11111011
+# xxyyyzzz
+
+ print_3('ei', '', 'Enable interrupts.');
+ }
+ } # given ($dcd_instr_y)
+ } # $dcd_instr_z == 3
+
+ when (4)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 1 | . | . | . | 1 | 0 | 0 |
+ # +-----------------------------------------------+
+
+# CALL cc, nn xx nn nn 11ccc100 a7-0 a15-8
+# xxyyyzzz
+# cc: NZ, Z, NC, C, PO, PE, P, M
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $addr = ($dcd_parm1 << 8) | $dcd_parm0;
+ my $cond = $conditions[$dcd_instr_y];
+ my $target;
+
+ $str = label_name($addr);
+ $target = jump_direction($addr);
+ print_3('call', "$cond->{COND}, $str", "Calls ([--SP] = PC.h; [--SP] = PC.l) if ($cond->{EXPL})$target");
+ }
+ } # $dcd_instr_z == 4
+
+ when (5)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 1 | . | . | . | 1 | 0 | 1 |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # +---\ /---+ +\ /+
+ # p q
+
+ if ($dcd_instr_q == 0)
+ {
+ # x z
+ # +---/ \---+ +------/ \------+
+ # | 7 6 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 1 | . | . | 0 | 1 | 0 | 1 |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # +---\ /---+ +\ /+
+ # p q
+
+# PUSH rp xx 11rr0101
+# xxppqzzz
+# rp: BC, DE, HL, AF
+
+ given ($dcd_instr_p)
+ {
+ when (0) { $str = '[--SP] = B; [--SP] = C'; }
+ when (1) { $str = '[--SP] = D; [--SP] = E'; }
+ when (2) { $str = '[--SP] = H; [--SP] = L'; }
+ when (3) { $str = '[--SP] = A; [--SP] = F'; }
+ }
+
+ print_3('push', $core_registers16b[$dcd_instr_p], $str);
+ }
+ else
+ {
+ # x z
+ # +---/ \---+ +------/ \------+
+ # | 7 6 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 1 | . | . | 1 | 1 | 0 | 1 |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # +---\ /---+ +\ /+
+ # p q
+
+ given ($dcd_instr_p)
+ {
+ when (0)
+ {
+ # x z
+ # +---/ \---+ +------/ \------+
+ # | 7 6 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # +---\ /---+ +\ /+
+ # p q
+
+# CALL nn CD nn nn 11001101 a7-0 a15-8
+# xxyyyzzz
+
+ if ($decoder_silent_level == SILENT0)
+ {
+ my $addr = ($dcd_parm1 << 8) | $dcd_parm0;
+ my $target;
+
+ $str = label_name($addr);
+ $target = jump_direction($addr);
+ print_3('call', $str, "Calls ([--SP] = PC.h; [--SP] = PC.l)$target");
+ }
+ }
+
+ when (1)
+ {
+ # x z
+ # +---/ \---+ +------/ \------+
+ # | 7 6 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 1 | 0 | 1 | 1 | 1 | 0 | 1 |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # +---\ /---+ +\ /+
+ # p q
+
+ DDFD_prefix_decoder('IX');
+ }
+
+ when (2)
+ {
+ # x z
+ # +---/ \---+ +------/ \------+
+ # | 7 6 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 1 | 1 | 0 | 1 | 1 | 0 | 1 |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # +---\ /---+ +\ /+
+ # p q
+
+ ED_prefix_decoder();
+ }
+
+ when (3)
+ {
+ # x z
+ # +---/ \---+ +------/ \------+
+ # | 7 6 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 |
+ # +-----------------------------------------------+
+ # | 5 4 | | 3 |
+ # +---\ /---+ +\ /+
+ # p q
+
+ DDFD_prefix_decoder('IY');
+ }
+ } # given ($dcd_instr_p)
+ }
+ } # $dcd_instr_z == 5
+
+ when (6)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 1 | . | . | . | 1 | 1 | 0 |
+ # +-----------------------------------------------+
+
+# ADD A, #n 11000110 nnnnnnnn
+# ADC A, #n 11001110 nnnnnnnn
+# SUB A, #n 11010110 nnnnnnnn
+# SBC A, #n 11011110 nnnnnnnn
+# AND A, #n 11100110 nnnnnnnn
+# XOR A, #n 11101110 nnnnnnnn
+# OR A, #n 11110110 nnnnnnnn
+# CP A, #n 11111110 nnnnnnnn
+# xxyyyzzz
+
+ my $i_arith = $DDFD_instr[$dcd_instr_y];
+ my $num = sprintf '0x%02X', $dcd_parm0;
+ my $char = decode_char($dcd_parm0);
+ my $str0 = '';
+
+ $str0 = ' (A = A)' if ($dcd_instr_y == 0 && $dcd_parm0 == 0); # ADD A, 0
+ $str0 = ' (A = A)' if ($dcd_instr_y == 2 && $dcd_parm0 == 0); # SUB A, 0
+ $str0 = ' (A = A)' if ($dcd_instr_y == 4 && $dcd_parm0 == 0xFF); # AND A, 0xFF
+ $str0 = ' (A = ~A)' if ($dcd_instr_y == 5 && $dcd_parm0 == 0xFF); # XOR A, 0xFF
+ $str0 = ' (A = A)' if ($dcd_instr_y == 6 && $dcd_parm0 == 0); # OR A, 0
+
+ print_3($i_arith->{INSTR}, "A, #$num", "$i_arith->{EXPL} $num$char$str0");
+ } # $dcd_instr_z == 6
+
+ when (7)
+ {
+ # x y z
+ # +---/ \---+ +------/ \------+ +------/ \------+
+ # | 7 6 | | 5 4 3 | | 2 1 0 |
+ # +-----------------------------------------------+
+ # | 1 | 1 | . | . | . | 1 | 1 | 1 |
+ # +-----------------------------------------------+
+
+# RST t xx 11ttt111
+# xxyyyzzz
+# t: 0 - 7
+
+ $addr = sprintf '0x%04X', $dcd_instr_y * 8;
+ $str = sprintf '0x%02X', $dcd_instr_y * 8;
+ print_3('rst', $str, "Calls interrupt: [--SP] = PC.h; [--SP] = PC.l; PC = $addr");
+ } # $dcd_instr_z == 7
+ } # given ($dcd_instr_z)
+ } # $dcd_instr_x == 3
+ }
+
+################################################################################
+################################################################################
+
+ #
+ # Reads the sfrs and bits from the $Line.
+ #
+
+sub process_header_line($)
+ {
+ my $Line = $_[0];
+
+ Log((' ' x $embed_level) . $Line, 5);
+
+ if ($Line =~ /^#\s*include\s+["<]\s*(\S+)\s*[">]$/o)
+ {
+ $embed_level += 4;
+ &read_header("$include_path/$1");
+ $embed_level -= 4;
+ }
+ elsif ($Line =~ /^__sfr\s+__at\s*(?:\(\s*)?0x([[:xdigit:]]+)(?:\s*\))?\s+([\w_]+)/io)
+ {
+ # __sfr __at (0x80) P0 ; /* PORT 0 */
+
+ add_ram(hex($1), $2, TRUE);
+ }
+ elsif ($Line =~ /^SFR\s*\(\s*([\w_]+)\s*,\s*0x([[:xdigit:]]+)\s*\)/io)
+ {
+ # SFR(P0, 0x80); // Port 0
+
+ add_ram(hex($2), $1, TRUE);
+ }
+ elsif ($Line =~ /^sfr\s+([\w_]+)\s*=\s*0x([[:xdigit:]]+)/io)
+ {
+ # sfr P1 = 0x90;
+
+ add_ram(hex($2), $1, TRUE);
+ }
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Reads in a MCU.h file.
+ #
+
+sub read_header($)
+ {
+ my $Header = $_[0];
+ my ($fh, $pre_comment, $comment, $line_number);
+ my $head;
+
+ if (! open($fh, '<', $Header))
+ {
+ print STDERR "$PROGRAM: Could not open. -> \"$Header\"\n";
+ exit(1);
+ }
+
+ $head = ' ' x $embed_level;
+
+ Log("${head}read_header($Header) >>>>", 5);
+
+ $comment = FALSE;
+ $line_number = 1;
+ while (<$fh>)
+ {
+ chomp;
+ s/\r$//o; # '\r'
+
+ # Filters off the C comments.
+
+ s/\/\*.*\*\///o; # /* ... */
+ s/\/\/.*$//o; # // ...
+ s/^\s*|\s*$//go;
+
+ if (/\/\*/o) # /*
+ {
+ $pre_comment = TRUE;
+ s/\s*\/\*.*$//o;
+ }
+ elsif (/\*\//o) # */
+ {
+ $pre_comment = FALSE;
+ $comment = FALSE;
+ s/^.*\*\/\s*//o;
+ }
+
+ if ($comment)
+ {
+ ++$line_number;
+ next;
+ }
+
+ $comment = $pre_comment if ($pre_comment);
+
+ if (/^\s*$/o)
+ {
+ ++$line_number;
+ next;
+ }
+
+ run_preprocessor($Header, \&process_header_line, $_, $line_number);
+ ++$line_number;
+ } # while (<$fh>)
+
+ Log("${head}<<<< read_header($Header)", 5);
+ close($fh);
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Determines size of the $dcd_instr.
+ #
+
+sub determine_instr_size()
+ {
+ my $instr;
+ my $size = $instruction_sizes_[$dcd_instr];
+
+ return $size if ($size >= 0);
+
+ $instr = $rom[$dcd_address + 1];
+
+ if ($size == IPREFIX_DD || $size == IPREFIX_FD)
+ {
+ return $instruction_sizes_DDFD[$instr];
+ }
+ elsif ($size == IPREFIX_ED)
+ {
+ return $instruction_sizes_ED[$instr];
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Among the blocks stows description of an instruction.
+ #
+
+sub add_instr_block($)
+ {
+ my $Address = $_[0];
+ my ($instr_size, $invalid);
+
+ $dcd_address = $Address;
+ $dcd_instr = $rom[$dcd_address];
+ $invalid = FALSE;
+
+ $instr_size = determine_instr_size();
+
+ if ($instr_size == 0)
+ {
+ $instr_size = 1;
+ add_block($Address, BLOCK_CONST, $instr_size, BL_TYPE_NONE, '');
+ }
+ else
+ {
+ if ($instr_size == 1)
+ {
+ $invalid = TRUE if ($dcd_instr == EMPTY);
+ }
+
+ if ($instr_size == 2)
+ {
+ $invalid = TRUE if ($rom[$dcd_address + 1] == EMPTY);
+ }
+
+ if ($instr_size == 3)
+ {
+ $invalid = TRUE if ($rom[$dcd_address + 2] == EMPTY);
+ }
+
+ if ($instr_size == 4)
+ {
+ $invalid = TRUE if ($rom[$dcd_address + 3] == EMPTY);
+ }
+
+ if ($invalid)
+ {
+ add_block($Address, BLOCK_CONST, $instr_size, BL_TYPE_NONE, '');
+ }
+ else
+ {
+ add_block($Address, BLOCK_INSTR, $instr_size, BL_TYPE_NONE, '');
+ }
+ }
+
+ return $instr_size;
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Splits the program into small blocks.
+ #
+
+sub split_code_to_blocks()
+ {
+ my ($i, $instr);
+ my ($is_empty, $empty_begin);
+ my ($is_const, $const_begin);
+
+ $is_empty = FALSE;
+ $is_const = FALSE;
+
+ for ($i = 0; $i < $rom_size; )
+ {
+ $instr = $rom[$i];
+
+ if ($instr == EMPTY)
+ {
+ if (! $is_empty)
+ {
+ # The begin of the empty section.
+
+ if ($is_const)
+ {
+ # The end of the constant section.
+
+ add_block($const_begin, BLOCK_CONST, $i - $const_begin, BL_TYPE_NONE, '');
+ $is_const = FALSE;
+ }
+
+ $empty_begin = $i;
+ $is_empty = TRUE;
+ }
+
+ ++$i;
+ } # if ($instr == EMPTY)
+ elsif (is_constant($i))
+ {
+ if (! $is_const)
+ {
+ if ($is_empty)
+ {
+ # The end of the empty section.
+
+ add_block($empty_begin, BLOCK_EMPTY, $i - $empty_begin, BL_TYPE_NONE, '');
+ $is_empty = FALSE;
+ }
+
+ $const_begin = $i;
+ $is_const = TRUE;
+ }
+
+ ++$i;
+ } # elsif (is_constant($i))
+ else
+ {
+ if ($is_const)
+ {
+ # The end of the constant section.
+
+ add_block($const_begin, BLOCK_CONST, $i - $const_begin, BL_TYPE_NONE, '');
+ $is_const = FALSE;
+ }
+
+ if ($is_empty)
+ {
+ # The end of the empty section.
+
+ add_block($empty_begin, BLOCK_EMPTY, $i - $empty_begin, BL_TYPE_NONE, '');
+ $is_empty = FALSE;
+ }
+
+ $i += add_instr_block($i);
+ }
+ } # for ($i = 0; $i < $rom_size; )
+
+ if ($is_const)
+ {
+ add_block($const_begin, BLOCK_CONST, $i - $const_begin, BL_TYPE_NONE, '');
+ }
+
+ if ($is_empty)
+ {
+ add_block($empty_begin, BLOCK_EMPTY, $i - $empty_begin, BL_TYPE_NONE, '');
+ }
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Previously assess the code.
+ #
+
+sub preliminary_survey($)
+ {
+ $decoder_silent_level = $_[0];
+ foreach (sort {$a <=> $b} keys(%blocks_by_address))
+ {
+ my $block = \%{$blocks_by_address{$_}};
+
+ next if ($block->{TYPE} != BLOCK_INSTR);
+
+ instruction_decoder($_, $block);
+ }
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Finds address of branchs and procedures.
+ #
+
+sub find_labels_in_code()
+ {
+ foreach (sort {$a <=> $b} keys(%blocks_by_address))
+ {
+ my $block = \%{$blocks_by_address{$_}};
+
+ next if ($block->{TYPE} != BLOCK_INSTR);
+
+ label_finder($_, $block);
+ }
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Finds lost address of branchs and procedures.
+ #
+
+sub find_lost_labels_in_code()
+ {
+ my ($block, $prev_block, $prev_addr, $label, $instr);
+
+ $prev_addr = EMPTY;
+ $prev_block = undef;
+ foreach (sort {$a <=> $b} keys(%blocks_by_address))
+ {
+ $block = \%{$blocks_by_address{$_}};
+
+ last if ($block->{TYPE} == BLOCK_RAM);
+ next if ($block->{TYPE} != BLOCK_INSTR);
+
+ if ($prev_addr != EMPTY)
+ {
+ $instr = $rom[$prev_addr];
+ $label = $block->{LABEL};
+
+ if (defined($label) && $label->{TYPE} == BL_TYPE_NONE)
+ {
+# if ($instr == INST_RET || $instr == INST_RETI)
+ if ($instr == INST_RET)
+ {
+ Log(sprintf("Lost function label at the 0x%04X address.", $_), 5);
+ add_func_label($_, '', TRUE);
+ }
+ elsif ($instr == INST_JP || $instr == INST_JR || $instr == INST_JP_HL)
+ {
+ Log(sprintf("Lost jump label at the 0x%04X address.", $_), 5);
+ add_jump_label($_, '', BL_TYPE_LABEL, EMPTY, TRUE);
+ }
+ }
+ }
+
+ $prev_addr = $_;
+ $prev_block = $block;
+ }
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Jump tables looking for in the code.
+ #
+
+sub recognize_jump_tables_in_code()
+ {
+ my @blocks = ((undef) x 5);
+ my @instrs = ((EMPTY) x 5);
+ my ($addr);
+
+ foreach (sort {$a <=> $b} keys(%blocks_by_address))
+ {
+ shift(@instrs);
+ push(@instrs, $rom[$_]);
+
+ shift(@blocks);
+ push(@blocks, \%{$blocks_by_address{$_}});
+
+ next if (! defined($blocks[0]) || ! defined($blocks[4]));
+ next if ($blocks[0]->{TYPE} != BLOCK_INSTR);
+ next if ($blocks[1]->{TYPE} != BLOCK_INSTR);
+ next if ($blocks[2]->{TYPE} != BLOCK_INSTR);
+ next if ($blocks[3]->{TYPE} != BLOCK_INSTR);
+ next if ($blocks[4]->{TYPE} != BLOCK_INSTR);
+
+ if ($blocks[0]->{SIZE} == 3 && $instrs[0] == INST_LD_HL &&
+ $blocks[1]->{SIZE} == 1 && $instrs[1] == INST_ADD_HL_DE &&
+ $blocks[2]->{SIZE} == 1 && $instrs[2] == INST_ADD_HL_DE &&
+ $blocks[3]->{SIZE} == 1 &&
+ (($instrs[3] == INST_ADD_HL_DE && $blocks[4]->{SIZE} == 1 && $instrs[4] == INST_JP_HL) ||
+ $instrs[3] == INST_JP_HL))
+ {
+=back
+0x019D: 21 A4 01 ld HL, #0x01A4 ; HL = 0x01A4
+0x01A0: 19 add HL, DE ; HL += DE
+0x01A1: 19 add HL, DE ; HL += DE
+0x01A2: 19 add HL, DE ; HL += DE
+0x01A3: E9 jp (HL) ; Jumps to value of HL.
+
+0x01A4: C3 D4 01 jp Label_021 ; Jumps (forward) hither: 0x01D4
+
+----------------------------------------------------------------------------------------------------
+
+0x019D: 21 A4 01 ld HL, #0x01A3 ; HL = 0x01A4
+0x01A0: 19 add HL, DE ; HL += DE
+0x01A1: 19 add HL, DE ; HL += DE
+0x01A2: E9 jp (HL) ; Jumps to value of HL.
+
+0x01A3: 18 2F jr Label_021 ; Jumps (forward) hither: 0x01D4
+=cut
+
+ $addr = ($rom[$blocks[0]->{ADDR} + 2] << 8) | $rom[$blocks[0]->{ADDR} + 1];
+ add_jump_label($addr, '', BL_TYPE_JTABLE, EMPTY, FALSE);
+ }
+ }
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Prints the global symbols.
+ #
+
+sub emit_globals($)
+ {
+ my $Assembly_mode = $_[0];
+ my ($label, $cnt0, $cnt1, $str0, $str1);
+
+ return if (! scalar(keys(%labels_by_address)));
+
+ print ";$border0\n;\tPublic labels\n;$border0\n\n";
+
+ if ($Assembly_mode)
+ {
+ foreach (sort {$a <=> $b} keys(%labels_by_address))
+ {
+ $label = $labels_by_address{$_};
+
+ next if ($label->{TYPE} != BL_TYPE_SUB);
+
+ print "\t.globl\t$label->{NAME}\n";
+ }
+ }
+ else
+ {
+ foreach (sort {$a <=> $b} keys(%labels_by_address))
+ {
+ $label = $labels_by_address{$_};
+
+ next if ($label->{TYPE} != BL_TYPE_SUB);
+
+ $str0 = sprintf "0x%04X", $_;
+ $cnt0 = sprintf "%3u", $label->{CALL_COUNT};
+ $cnt1 = sprintf "%3u", $label->{JUMP_COUNT};
+ $str1 = ($label->{CALL_COUNT} || $label->{JUMP_COUNT}) ? "calls: $cnt0, jumps: $cnt1" : 'not used';
+ print "${str0}:\t" . align($label->{NAME}, STAT_ALIGN_SIZE) . "($str1)\n";
+ }
+ }
+
+ print "\n";
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Prints the registers (variables).
+ #
+
+sub emit_ram_data()
+ {
+ my ($block, $first, $name, $next_addr, $size, $cnt, $str0, $str1);
+
+ return if (! scalar(keys(%ram_blocks_by_address)));
+
+ print ";$border0\n;\tRAM data\n;$border0\n\n";
+
+ $next_addr = EMPTY;
+ foreach (sort {$a <=> $b} keys(%ram_blocks_by_address))
+ {
+ $block = $blocks_by_address{$_};
+
+ if ($block->{TYPE} != BLOCK_RAM)
+ {
+ $next_addr = EMPTY;
+ next;
+ }
+
+ next if ($next_addr != EMPTY && $_ < $next_addr);
+
+ $str0 = sprintf "0x%04X", $_;
+ $cnt = sprintf "%3u", $block->{REF_COUNT};
+ $str1 = ($block->{REF_COUNT}) ? "used $cnt times" : 'not used';
+ $name = $ram_names_by_address{$_};
+
+ if (defined($name) && $name ne '')
+ {
+ $cnt = sprintf "%5u", $block->{SIZE};
+ print "${str0}:\t" . align($name, STAT_ALIGN_SIZE) . "($cnt bytes) ($str1)\n";
+ $next_addr = $_ + $block->{SIZE};
+ }
+ else
+ {
+ if ($map_readed)
+ {
+ print "${str0}:\t" . align("variable_$str0", STAT_ALIGN_SIZE) . "( 1 bytes) ($str1)\n";
+ }
+ else
+ {
+ print "${str0}:\t" . align("variable_$str0", STAT_ALIGN_SIZE) . "($str1)\n";
+ }
+
+ $next_addr = $_ + 1;
+ }
+ } # foreach (sort {$a <=> $b} keys(%ram_blocks_by_address))
+
+ print "\n";
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Prints I/O ports.
+ #
+
+sub emit_io_ports()
+ {
+ my ($io, $cnt, $str0, $str1);
+
+ return if (! scalar(keys(%io_by_address)));
+
+ print ";$border0\n;\tI/O ports\n;$border0\n\n";
+
+ foreach (sort {$a <=> $b} keys(%io_by_address))
+ {
+ $io = $io_by_address{$_};
+
+ $str0 = sprintf "0x%02X", $_;
+ $cnt = sprintf "%3u", $io->{REF_COUNT};
+ $str1 = ($io->{REF_COUNT}) ? "used $cnt times" : 'not used';
+
+ if ($io->{NAME} ne '')
+ {
+ print "${str0}:\t" . align($io->{NAME}, STAT_ALIGN_SIZE) . "($str1)\n";
+ }
+ else
+ {
+ print "${str0}:\t" . align("port_$str0", STAT_ALIGN_SIZE) . "($str1)\n";
+ }
+ } # foreach (sort {$a <=> $b} keys(%io_by_address))
+
+ print "\n";
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Prints a label belonging to the $Address.
+ #
+
+sub print_label($)
+ {
+ my $Address = $_[0];
+ my ($label, $type);
+
+ $label = $labels_by_address{$Address};
+
+ return FALSE if (! defined($label) || $label->{TYPE} == BL_TYPE_NONE);
+
+ $type = $label->{TYPE};
+
+ print "\n;$border0\n" if ($type == BL_TYPE_SUB);
+
+ printf "\n$label->{NAME}:\n\n";
+ $label->{PRINTED} = TRUE;
+ $prev_is_jump = FALSE;
+ return TRUE;
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Prints a variable belonging to the $Address.
+ #
+
+sub print_variable($$)
+ {
+ my ($Address, $BlockRef) = @_;
+ my ($name, $size, $str0, $str1);
+
+ $size = $BlockRef->{SIZE};
+
+ return if (! $size);
+
+ $name = $ram_names_by_address{$Address};
+
+ return if (! defined($name) || $name eq '');
+
+ $str0 = sprintf "0x%04X", $Address;
+
+ given ($size)
+ {
+ when (1) { $str1 = '.db'; }
+ when (2) { $str1 = '.dw'; }
+ when (4) { $str1 = '.dd'; }
+ when (8) { $str1 = '.dq'; }
+ when (10) { $str1 = '.dt'; }
+ default { $str1 = '.db'; }
+ }
+
+ if ($gen_assembly_code)
+ {
+ print "$name:\n";
+ $str0 = "\t$str1\t?";
+ }
+ else
+ {
+ $str0 = align("$str0:$name", RAM_ALIGN_SIZE) . "$str1\t?";
+ }
+
+ print align($str0, RAM_ALIGN_SIZE + 1 + EXPL_ALIGN_SIZE) . "; $size bytes\n";
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Prints a table of constants.
+ #
+
+sub print_constants($$)
+ {
+ my ($Address, $BlockRef) = @_;
+ my ($size, $i, $len, $frag, $byte, $spc, $col, $brd);
+ my ($left_align, $right_align);
+ my @constants;
+ my @line;
+
+ $size = $BlockRef->{SIZE};
+
+ return if (! $size);
+
+ $prev_is_jump = FALSE;
+ $col = ' ';
+
+ if ($gen_assembly_code)
+ {
+ print ";$table_border\n;\t\t $table_header | $table_header |\n;$table_border\n";
+ $brd = ' ';
+ }
+ else
+ {
+ print "$table_border\n| | $table_header | $table_header |\n$table_border\n";
+ $brd = '|';
+ }
+
+ @constants = @rom[$Address .. ($Address + $size - 1)];
+ $i = 0;
+ while (TRUE)
+ {
+ $len = $size - $i;
+
+ last if (! $len);
+
+ $len = TBL_COLUMNS if ($len > TBL_COLUMNS);
+
+ if ($gen_assembly_code)
+ {
+ print "\t.db\t";
+ }
+ else
+ {
+ printf "$brd 0x%04X $brd ", $Address;
+ }
+
+ if (($spc = $Address % TBL_COLUMNS))
+ {
+ $frag = TBL_COLUMNS - $spc;
+ $len = $frag if ($len > $frag);
+ }
+
+ $left_align = $col x $spc;
+ $right_align = $col x (TBL_COLUMNS - $spc - $len);
+ @line = @constants[$i .. ($i + $len - 1)];
+ $Address += $len;
+ $i += $len;
+
+ print " $left_align" . join(' ', map { sprintf("%02X ", $_ & 0xFF); } @line);
+
+ print "$right_align $brd $left_align " .
+ join(' ', map {
+ sprintf((($_ < ord(' ') || $_ >= 0x7F) ? "%02X " : "'%c'"), $_ & 0xFF);
+ } @line) . "$right_align $brd\n";
+ } # while (TRUE)
+
+ print (($gen_assembly_code) ? ";$table_border\n" : "$table_border\n");
+ $prev_is_jump = FALSE;
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # Disassembly contents of $blocks_by_address array.
+ #
+
+sub disassembler()
+ {
+ my ($sname, $prev_block_type, $ref);
+
+ $prev_is_jump = FALSE;
+ $decoder_silent_level = SILENT0;
+
+ $table_header = join(' ', map { sprintf '%02X', $_ } (0 .. (TBL_COLUMNS - 1)));
+
+ if ($gen_assembly_code)
+ {
+ $table_border = ('-' x (TBL_COLUMNS * 4 + 16)) . '+' . ('-' x (TBL_COLUMNS * 4 + 2)) . '+';
+ }
+ else
+ {
+ $table_border = '+' . ('-' x 10) . '+' . ('-' x (TBL_COLUMNS * 4 + 2)) . '+' . ('-' x (TBL_COLUMNS * 4 + 2)) . '+';
+ }
+
+ print "\n";
+
+ if ($gen_assembly_code)
+ {
+ emit_globals(TRUE);
+ print ";$border0\n;\tCode\n;$border0\n\n\t.area\tCODE\t(CODE)\n\n";
+ }
+ else
+ {
+ emit_globals(FALSE);
+ emit_ram_data();
+ emit_io_ports();
+ print ";$border0\n";
+ }
+
+ $prev_block_type = EMPTY;
+ foreach (sort {$a <=> $b} keys(%blocks_by_address))
+ {
+ $ref = $blocks_by_address{$_};
+
+ if ($ref->{TYPE} == BLOCK_INSTR)
+ {
+ print_label($_);
+ print "\n" if ($prev_is_jump);
+
+ instruction_decoder($_, $ref);
+ $prev_block_type = BLOCK_INSTR;
+ }
+ elsif ($ref->{TYPE} == BLOCK_RAM)
+ {
+ print "\n;$border0\n\n" if ($prev_block_type != BLOCK_RAM);
+
+ print_variable($_, $ref);
+ $prev_block_type = BLOCK_RAM;
+ }
+ elsif ($ref->{TYPE} == BLOCK_CONST)
+ {
+ print "\n;$border0\n" if ($prev_block_type != BLOCK_CONST);
+
+ print_label($_);
+ print "\n" if ($prev_is_jump);
+
+ print_constants($_, $ref);
+ $prev_block_type = BLOCK_CONST;
+ }
+ elsif ($ref->{TYPE} == BLOCK_EMPTY)
+ {
+ my $next_block = $_ + $ref->{SIZE};
+
+ print "\n;$border0\n" if ($prev_block_type != BLOCK_EMPTY);
+
+ if (! $gen_assembly_code)
+ {
+ printf("\n0x%04X: -- -- --\n .... -- -- --\n0x%04X: -- -- --\n", $_, $next_block - 1);
+ }
+ elsif ($next_block <= $rom_size)
+ {
+ # Skip the empty code space.
+
+ printf "\n\t.ds\t%u\n", $ref->{SIZE};
+ }
+
+ $prev_block_type = BLOCK_EMPTY;
+ }
+ } # foreach (sort {$a <=> $b} keys(%blocks_by_address))
+ }
+
+#-------------------------------------------------------------------------------
+
+ #
+ # If there are datas in the code, it is possible that some labels will
+ # be lost. This procedure prints them.
+ #
+
+sub print_hidden_labels()
+ {
+ foreach (sort {$a <=> $b} keys(%labels_by_address))
+ {
+ my $label = $labels_by_address{$_};
+
+ print STDERR "The label: $label->{NAME} is hidden!\n" if (! $label->{PRINTED});
+ }
+ }
+
+################################################################################
+################################################################################
+
+sub usage()
+ {
+ print <<EOT;
+Usage: $PROGRAM [options] <hex file>
+
+ Options are:
+
+ -M|--mcu <header.h>
+
+ Header file of the MCU.
+
+ -I|--include <path to header>
+
+ Path of the header files of Z80 MCUs. (Default: $default_include_path)
+
+ --map-file <file.map>
+
+ The map file belonging to the input hex file. (optional)
+
+ -r|--rom-size <size of program memory>
+
+EOT
+;
+ printf "\t Defines size of the program memory. (Default %u bytes.)\n", Z80_ROM_SIZE;
+ print <<EOT;
+
+ --const-area <start address> <end address>
+
+ Designates a constant area (jumptables, texts, etc.), where data is
+ stored happen. The option may be given more times, that to select
+ more areas at the same time. (optional)
+
+ -as|--assembly-source
+
+ Generates the assembly source file. (Eliminates before the instructions
+ visible address and hex codes.) Emits global symbol table, etc.
+
+ -fl|--find-lost-labels
+
+ Finds the "lost" labels. These may be found such in program parts,
+ which are directly not get call.
+
+ --name-list <list_file>
+
+ The file contains list of names. They may be: Names of variables and
+ names of labels. For example:
+
+ [IO]
+ 0x21:keyboard_io
+ ..
+ ..
+ ..
+ [RAM]
+ 0x8021:ram_variable
+ ..
+ ..
+ ..
+ [ROM]
+ 0x05FC:function_or_label
+ ..
+ ..
+ ..
+
+ The contents of list override the names from map file.
+
+ -ne|--no-explanations
+
+ Eliminates after the instructions visible explaining texts.
+
+ -v <level> or --verbose <level>
+
+ It provides information on from the own operation.
+ Possible value of the level between 0 and 10. (default: 0)
+
+ -h|--help
+
+ This text.
+EOT
+;
+ }
+
+################################################################################
+################################################################################
+################################################################################
+
+foreach (@default_paths)
+ {
+ if (-d $_)
+ {
+ $default_include_path = $_;
+ last;
+ }
+ }
+
+if (! @ARGV)
+ {
+ usage();
+ exit(1);
+ }
+
+for (my $i = 0; $i < @ARGV; )
+ {
+ my $opt = $ARGV[$i++];
+
+ given ($opt)
+ {
+ when (/^-(r|-rom-size)$/o)
+ {
+ param_exist($opt, $i);
+ $rom_size = str2int($ARGV[$i++]);
+
+ if ($rom_size < 1024)
+ {
+ printf STDERR "$PROGRAM: Code size of the Z80 family greater than 1024 bytes!\n";
+ exit(1);
+ }
+ elsif ($rom_size > Z80_ROM_SIZE)
+ {
+ printf STDERR "$PROGRAM: Code size of the Z80 family not greater %u bytes!\n", Z80_ROM_SIZE;
+ exit(1);
+ }
+ }
+
+ when (/^--const-area$/o)
+ {
+ my ($start, $end);
+
+ param_exist($opt, $i);
+ $start = str2int($ARGV[$i++]);
+
+ param_exist($opt, $i);
+ $end = str2int($ARGV[$i++]);
+
+ if ($start > $end)
+ {
+ my $t = $start;
+
+ $start = $end;
+ $end = $t;
+ }
+ elsif ($start == $end)
+ {
+ $start = Z80_ROM_SIZE - 1;
+ $end = Z80_ROM_SIZE - 1;
+ }
+
+ add_const_area($start, $end) if ($start < $end);
+ } # when (/^--const-area$/o)
+
+ when (/^-(I|-include)$/o)
+ {
+ param_exist($opt, $i);
+ $include_path = $ARGV[$i++];
+ }
+
+ when (/^-(M|-mcu)$/o)
+ {
+ param_exist($opt, $i);
+ $header_file = $ARGV[$i++];
+ }
+
+ when (/^--map-file$/o)
+ {
+ param_exist($opt, $i);
+ $map_file = $ARGV[$i++];
+ }
+
+ when (/^-(as|-assembly-source)$/o)
+ {
+ $gen_assembly_code = TRUE;
+ }
+
+ when (/^-(fl|-find-lost-labels)$/o)
+ {
+ $find_lost_labels = TRUE;
+ }
+
+ when (/^--name-list$/o)
+ {
+ param_exist($opt, $i);
+ $name_list = $ARGV[$i++];
+ }
+
+ when (/^-(ne|-no-explanations)$/o)
+ {
+ $no_explanations = TRUE;
+ }
+
+ when (/^-(v|-verbose)$/o)
+ {
+ param_exist($opt, $i);
+ $verbose = int($ARGV[$i++]);
+ $verbose = 0 if (! defined($verbose) || $verbose < 0);
+ $verbose = 10 if ($verbose > 10);
+ }
+
+ when (/^-(h|-help)$/o)
+ {
+ usage();
+ exit(0);
+ }
+
+ default
+ {
+ if ($hex_file eq '')
+ {
+ $hex_file = $opt;
+ }
+ else
+ {
+ print STDERR "$PROGRAM: We already have the source file name: $hex_file.\n";
+ exit(1);
+ }
+ }
+ } # given ($opt)
+ } # for (my $i = 0; $i < @ARGV; )
+
+$include_path = $default_include_path if ($include_path eq '');
+
+if ($hex_file eq '')
+ {
+ print STDERR "$PROGRAM: What do you have to disassembled?\n";
+ exit(1);
+ }
+
+is_file_ok($hex_file);
+
+init_mem(0, $rom_size - 1);
+read_hex($hex_file);
+
+if ($header_file ne '')
+ {
+ is_file_ok("$include_path/$header_file");
+ reset_preprocessor();
+ $embed_level = 0;
+ read_header("$include_path/$header_file");
+ }
+
+if ($map_file eq '')
+ {
+ ($map_file) = ($hex_file =~ /^(.+)\.hex$/io);
+ $map_file .= '.map';
+ }
+
+$map_file = '' if (! -e $map_file);
+
+is_file_ok($name_list) if ($name_list ne '');
+
+###################################
+
+read_map_file();
+read_name_list();
+split_code_to_blocks();
+recognize_jump_tables_in_code();
+preliminary_survey(SILENT1);
+find_labels_in_code();
+find_lost_labels_in_code() if ($find_lost_labels);
+add_names_labels();
+fix_multi_byte_variables();
+fix_io_names();
+disassembler();
+print_hidden_labels() if ($verbose > 2);