1 /* ======================================================================== */
2 /* ========================= LICENSING & COPYRIGHT ======================== */
3 /* ======================================================================== */
8 * A portable Motorola M680x0 processor emulation engine.
9 * Copyright Karl Stenerud. All rights reserved.
10 * FPU and MMU by R. Belmont.
12 * Permission is hereby granted, free of charge, to any person obtaining a copy
13 * of this software and associated documentation files (the "Software"), to deal
14 * in the Software without restriction, including without limitation the rights
15 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 * copies of the Software, and to permit persons to whom the Software is
17 * furnished to do so, subject to the following conditions:
19 * The above copyright notice and this permission notice shall be included in
20 * all copies or substantial portions of the Software.
22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
33 /* ======================================================================== */
34 /* ============================ CODE GENERATOR ============================ */
35 /* ======================================================================== */
37 * This is the code generator program which will generate the opcode table
38 * and the final opcode handlers.
40 * It requires an input file to function (default m68k_in.c), but you can
41 * specify your own like so:
43 * m68kmake <output path> <input file>
45 * where output path is the path where the output files should be placed, and
46 * input file is the file to use for input.
48 * If you modify the input file greatly from its released form, you may have
49 * to tweak the configuration section a bit since I'm using static allocation
50 * to keep things simple.
53 * TODO: - build a better code generator for the move instruction.
54 * - Add callm and rtm instructions
55 * - Fix RTE to handle other format words
56 * - Add address error (and bus error?) handling
60 static const char g_version[] = "4.60";
62 /* ======================================================================== */
63 /* =============================== INCLUDES =============================== */
64 /* ======================================================================== */
74 /* ======================================================================== */
75 /* ============================= CONFIGURATION ============================ */
76 /* ======================================================================== */
78 #define M68K_MAX_PATH 1024
79 #define M68K_MAX_DIR 1024
81 #define MAX_LINE_LENGTH 200 /* length of 1 line */
82 #define MAX_BODY_LENGTH 300 /* Number of lines in 1 function */
83 #define MAX_REPLACE_LENGTH 30 /* Max number of replace strings */
84 #define MAX_INSERT_LENGTH 5000 /* Max size of insert piece */
85 #define MAX_NAME_LENGTH 30 /* Max length of ophandler name */
86 #define MAX_SPEC_PROC_LENGTH 4 /* Max length of special processing str */
87 #define MAX_SPEC_EA_LENGTH 5 /* Max length of specified EA str */
88 #define EA_ALLOWED_LENGTH 11 /* Max length of ea allowed str */
89 #define MAX_OPCODE_INPUT_TABLE_LENGTH 1000 /* Max length of opcode handler tbl */
90 #define MAX_OPCODE_OUTPUT_TABLE_LENGTH 3000 /* Max length of opcode handler tbl */
92 /* Default filenames */
93 #define FILENAME_INPUT "m68k_in.c"
94 #define FILENAME_PROTOTYPE "m68kops.h"
95 #define FILENAME_TABLE "m68kops.c"
98 /* Identifier sequences recognized by this program */
100 #define ID_INPUT_SEPARATOR "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
102 #define ID_BASE "M68KMAKE"
103 #define ID_PROTOTYPE_HEADER ID_BASE "_PROTOTYPE_HEADER"
104 #define ID_PROTOTYPE_FOOTER ID_BASE "_PROTOTYPE_FOOTER"
105 #define ID_TABLE_HEADER ID_BASE "_TABLE_HEADER"
106 #define ID_TABLE_FOOTER ID_BASE "_TABLE_FOOTER"
107 #define ID_TABLE_BODY ID_BASE "_TABLE_BODY"
108 #define ID_TABLE_START ID_BASE "_TABLE_START"
109 #define ID_OPHANDLER_HEADER ID_BASE "_OPCODE_HANDLER_HEADER"
110 #define ID_OPHANDLER_FOOTER ID_BASE "_OPCODE_HANDLER_FOOTER"
111 #define ID_OPHANDLER_BODY ID_BASE "_OPCODE_HANDLER_BODY"
112 #define ID_END ID_BASE "_END"
114 #define ID_OPHANDLER_NAME ID_BASE "_OP"
115 #define ID_OPHANDLER_EA_AY_8 ID_BASE "_GET_EA_AY_8"
116 #define ID_OPHANDLER_EA_AY_16 ID_BASE "_GET_EA_AY_16"
117 #define ID_OPHANDLER_EA_AY_32 ID_BASE "_GET_EA_AY_32"
118 #define ID_OPHANDLER_OPER_AY_8 ID_BASE "_GET_OPER_AY_8"
119 #define ID_OPHANDLER_OPER_AY_16 ID_BASE "_GET_OPER_AY_16"
120 #define ID_OPHANDLER_OPER_AY_32 ID_BASE "_GET_OPER_AY_32"
121 #define ID_OPHANDLER_CC ID_BASE "_CC"
122 #define ID_OPHANDLER_NOT_CC ID_BASE "_NOT_CC"
127 #endif /* DECL_SPEC */
131 /* ======================================================================== */
132 /* ============================== PROTOTYPES ============================== */
133 /* ======================================================================== */
144 #define UNSPECIFIED "."
145 #define UNSPECIFIED_CH '.'
147 #define HAS_NO_EA_MODE(A) (strcmp(A, "..........") == 0)
148 #define HAS_EA_AI(A) ((A)[0] == 'A')
149 #define HAS_EA_PI(A) ((A)[1] == '+')
150 #define HAS_EA_PD(A) ((A)[2] == '-')
151 #define HAS_EA_DI(A) ((A)[3] == 'D')
152 #define HAS_EA_IX(A) ((A)[4] == 'X')
153 #define HAS_EA_AW(A) ((A)[5] == 'W')
154 #define HAS_EA_AL(A) ((A)[6] == 'L')
155 #define HAS_EA_PCDI(A) ((A)[7] == 'd')
156 #define HAS_EA_PCIX(A) ((A)[8] == 'x')
157 #define HAS_EA_I(A) ((A)[9] == 'I')
161 EA_MODE_NONE, /* No special addressing mode */
162 EA_MODE_AI, /* Address register indirect */
163 EA_MODE_PI, /* Address register indirect with postincrement */
164 EA_MODE_PI7, /* Address register 7 indirect with postincrement */
165 EA_MODE_PD, /* Address register indirect with predecrement */
166 EA_MODE_PD7, /* Address register 7 indirect with predecrement */
167 EA_MODE_DI, /* Address register indirect with displacement */
168 EA_MODE_IX, /* Address register indirect with index */
169 EA_MODE_AW, /* Absolute word */
170 EA_MODE_AL, /* Absolute long */
171 EA_MODE_PCDI, /* Program counter indirect with displacement */
172 EA_MODE_PCIX, /* Program counter indirect with index */
173 EA_MODE_I /* Immediate */
177 /* Everything we need to know about an opcode */
180 char name[MAX_NAME_LENGTH]; /* opcode handler name */
181 unsigned char size; /* Size of operation */
182 char spec_proc[MAX_SPEC_PROC_LENGTH]; /* Special processing mode */
183 char spec_ea[MAX_SPEC_EA_LENGTH]; /* Specified effective addressing mode */
184 unsigned char bits; /* Number of significant bits (used for sorting the table) */
185 unsigned short op_mask; /* Mask to apply for matching an opcode to a handler */
186 unsigned short op_match; /* Value to match after masking */
187 char ea_allowed[EA_ALLOWED_LENGTH]; /* Effective addressing modes allowed */
188 char cpu_mode[NUM_CPUS]; /* User or supervisor mode */
189 char cpus[NUM_CPUS+1]; /* Allowed CPUs */
190 unsigned char cycles[NUM_CPUS]; /* cycles for 000, 010, 020, 030, 040 */
194 /* All modifications necessary for a specific EA mode of an instruction */
199 unsigned int mask_add;
200 unsigned int match_add;
204 /* Holds the body of a function */
207 char body[MAX_BODY_LENGTH][MAX_LINE_LENGTH+1];
212 /* Holds a sequence of search / replace strings */
215 char replace[MAX_REPLACE_LENGTH][2][MAX_LINE_LENGTH+1];
220 /* Function Prototypes */
221 void error_exit(char* fmt, ...);
222 void perror_exit(char* fmt, ...);
223 int check_strsncpy(char* dst, char* src, int maxlength);
224 int check_atoi(char* str, int *result);
225 int skip_spaces(char* str);
226 int num_bits(int value);
227 int atoh(char* buff);
228 int fgetline(char* buff, int nchars, FILE* file);
229 int get_oper_cycles(opcode_struct* op, int ea_mode, int cpu_type);
230 opcode_struct* find_opcode(char* name, int size, char* spec_proc, char* spec_ea);
231 opcode_struct* find_illegal_opcode(void);
232 int extract_opcode_info(char* src, char* name, int* size, char* spec_proc, char* spec_ea);
233 void add_replace_string(replace_struct* replace, char* search_str, char* replace_str);
234 void write_body(FILE* filep, body_struct* body, replace_struct* replace);
235 void get_base_name(char* base_name, opcode_struct* op);
236 void write_function_name(FILE* filep, char* base_name);
237 void add_opcode_output_table_entry(opcode_struct* op, char* name);
238 static int DECL_SPEC compare_nof_true_bits(const void* aptr, const void* bptr);
239 void print_opcode_output_table(FILE* filep);
240 void write_table_entry(FILE* filep, opcode_struct* op);
241 void set_opcode_struct(opcode_struct* src, opcode_struct* dst, int ea_mode);
242 void generate_opcode_handler(FILE* filep, body_struct* body, replace_struct* replace, opcode_struct* opinfo, int ea_mode);
243 void generate_opcode_ea_variants(FILE* filep, body_struct* body, replace_struct* replace, opcode_struct* op);
244 void generate_opcode_cc_variants(FILE* filep, body_struct* body, replace_struct* replace, opcode_struct* op_in, int offset);
245 void process_opcode_handlers(FILE* filep);
246 void populate_table(void);
247 void read_insert(char* insert);
251 /* ======================================================================== */
252 /* ================================= DATA ================================= */
253 /* ======================================================================== */
255 /* Name of the input file */
256 char g_input_filename[M68K_MAX_PATH] = FILENAME_INPUT;
259 FILE* g_input_file = NULL;
260 FILE* g_prototype_file = NULL;
261 FILE* g_table_file = NULL;
263 int g_num_functions = 0; /* Number of functions processed */
264 int g_num_primitives = 0; /* Number of function primitives read */
265 int g_line_number = 1; /* Current line number */
267 /* Opcode handler table */
268 opcode_struct g_opcode_input_table[MAX_OPCODE_INPUT_TABLE_LENGTH];
270 opcode_struct g_opcode_output_table[MAX_OPCODE_OUTPUT_TABLE_LENGTH];
271 int g_opcode_output_table_length = 0;
273 const ea_info_struct g_ea_info_table[13] =
274 {/* fname ea mask match */
275 {"", "", 0x00, 0x00}, /* EA_MODE_NONE */
276 {"ai", "AY_AI", 0x38, 0x10}, /* EA_MODE_AI */
277 {"pi", "AY_PI", 0x38, 0x18}, /* EA_MODE_PI */
278 {"pi7", "A7_PI", 0x3f, 0x1f}, /* EA_MODE_PI7 */
279 {"pd", "AY_PD", 0x38, 0x20}, /* EA_MODE_PD */
280 {"pd7", "A7_PD", 0x3f, 0x27}, /* EA_MODE_PD7 */
281 {"di", "AY_DI", 0x38, 0x28}, /* EA_MODE_DI */
282 {"ix", "AY_IX", 0x38, 0x30}, /* EA_MODE_IX */
283 {"aw", "AW", 0x3f, 0x38}, /* EA_MODE_AW */
284 {"al", "AL", 0x3f, 0x39}, /* EA_MODE_AL */
285 {"pcdi", "PCDI", 0x3f, 0x3a}, /* EA_MODE_PCDI */
286 {"pcix", "PCIX", 0x3f, 0x3b}, /* EA_MODE_PCIX */
287 {"i", "I", 0x3f, 0x3c}, /* EA_MODE_I */
291 const char *const g_cc_table[16][2] =
293 { "t", "T"}, /* 0000 */
294 { "f", "F"}, /* 0001 */
295 {"hi", "HI"}, /* 0010 */
296 {"ls", "LS"}, /* 0011 */
297 {"cc", "CC"}, /* 0100 */
298 {"cs", "CS"}, /* 0101 */
299 {"ne", "NE"}, /* 0110 */
300 {"eq", "EQ"}, /* 0111 */
301 {"vc", "VC"}, /* 1000 */
302 {"vs", "VS"}, /* 1001 */
303 {"pl", "PL"}, /* 1010 */
304 {"mi", "MI"}, /* 1011 */
305 {"ge", "GE"}, /* 1100 */
306 {"lt", "LT"}, /* 1101 */
307 {"gt", "GT"}, /* 1110 */
308 {"le", "LE"}, /* 1111 */
311 /* size to index translator (0 -> 0, 8 and 16 -> 1, 32 -> 2) */
312 const int g_size_select_table[33] =
315 0, 0, 0, 0, 0, 0, 0, 1, /* 8 */
316 0, 0, 0, 0, 0, 0, 0, 1, /* 16 */
317 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 /* 32 */
320 /* Extra cycles required for certain EA modes */
321 /* TODO: correct timings for 030, 040 */
322 const int g_ea_cycle_table[13][NUM_CPUS][3] =
323 {/* 000 010 020 030 040 */
324 {{ 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}}, /* EA_MODE_NONE */
325 {{ 0, 4, 8}, { 0, 4, 8}, { 0, 4, 4}, { 0, 4, 4}, { 0, 4, 4}}, /* EA_MODE_AI */
326 {{ 0, 4, 8}, { 0, 4, 8}, { 0, 4, 4}, { 0, 4, 4}, { 0, 4, 4}}, /* EA_MODE_PI */
327 {{ 0, 4, 8}, { 0, 4, 8}, { 0, 4, 4}, { 0, 4, 4}, { 0, 4, 4}}, /* EA_MODE_PI7 */
328 {{ 0, 6, 10}, { 0, 6, 10}, { 0, 5, 5}, { 0, 5, 5}, { 0, 5, 5}}, /* EA_MODE_PD */
329 {{ 0, 6, 10}, { 0, 6, 10}, { 0, 5, 5}, { 0, 5, 5}, { 0, 5, 5}}, /* EA_MODE_PD7 */
330 {{ 0, 8, 12}, { 0, 8, 12}, { 0, 5, 5}, { 0, 5, 5}, { 0, 5, 5}}, /* EA_MODE_DI */
331 {{ 0, 10, 14}, { 0, 10, 14}, { 0, 7, 7}, { 0, 7, 7}, { 0, 7, 7}}, /* EA_MODE_IX */
332 {{ 0, 8, 12}, { 0, 8, 12}, { 0, 4, 4}, { 0, 4, 4}, { 0, 4, 4}}, /* EA_MODE_AW */
333 {{ 0, 12, 16}, { 0, 12, 16}, { 0, 4, 4}, { 0, 4, 4}, { 0, 4, 4}}, /* EA_MODE_AL */
334 {{ 0, 8, 12}, { 0, 8, 12}, { 0, 5, 5}, { 0, 5, 5}, { 0, 5, 5}}, /* EA_MODE_PCDI */
335 {{ 0, 10, 14}, { 0, 10, 14}, { 0, 7, 7}, { 0, 7, 7}, { 0, 7, 7}}, /* EA_MODE_PCIX */
336 {{ 0, 4, 8}, { 0, 4, 8}, { 0, 2, 4}, { 0, 2, 4}, { 0, 2, 4}}, /* EA_MODE_I */
339 /* Extra cycles for JMP instruction (000, 010) */
340 const int g_jmp_cycle_table[13] =
342 0, /* EA_MODE_NONE */
352 6, /* EA_MODE_PCDI */
353 10, /* EA_MODE_PCIX */
357 /* Extra cycles for JSR instruction (000, 010) */
358 const int g_jsr_cycle_table[13] =
360 0, /* EA_MODE_NONE */
370 6, /* EA_MODE_PCDI */
371 10, /* EA_MODE_PCIX */
375 /* Extra cycles for LEA instruction (000, 010) */
376 const int g_lea_cycle_table[13] =
378 0, /* EA_MODE_NONE */
388 8, /* EA_MODE_PCDI */
389 12, /* EA_MODE_PCIX */
393 /* Extra cycles for PEA instruction (000, 010) */
394 const int g_pea_cycle_table[13] =
396 0, /* EA_MODE_NONE */
406 10, /* EA_MODE_PCDI */
407 14, /* EA_MODE_PCIX */
411 /* Extra cycles for MOVEM instruction (000, 010) */
412 const int g_movem_cycle_table[13] =
414 0, /* EA_MODE_NONE */
424 0, /* EA_MODE_PCDI */
425 0, /* EA_MODE_PCIX */
429 /* Extra cycles for MOVES instruction (010) */
430 const int g_moves_cycle_table[13][3] =
432 { 0, 0, 0}, /* EA_MODE_NONE */
433 { 0, 4, 6}, /* EA_MODE_AI */
434 { 0, 4, 6}, /* EA_MODE_PI */
435 { 0, 4, 6}, /* EA_MODE_PI7 */
436 { 0, 6, 12}, /* EA_MODE_PD */
437 { 0, 6, 12}, /* EA_MODE_PD7 */
438 { 0, 12, 16}, /* EA_MODE_DI */
439 { 0, 16, 20}, /* EA_MODE_IX */
440 { 0, 12, 16}, /* EA_MODE_AW */
441 { 0, 16, 20}, /* EA_MODE_AL */
442 { 0, 0, 0}, /* EA_MODE_PCDI */
443 { 0, 0, 0}, /* EA_MODE_PCIX */
444 { 0, 0, 0}, /* EA_MODE_I */
447 /* Extra cycles for CLR instruction (010) */
448 const int g_clr_cycle_table[13][3] =
450 { 0, 0, 0}, /* EA_MODE_NONE */
451 { 0, 4, 6}, /* EA_MODE_AI */
452 { 0, 4, 6}, /* EA_MODE_PI */
453 { 0, 4, 6}, /* EA_MODE_PI7 */
454 { 0, 6, 8}, /* EA_MODE_PD */
455 { 0, 6, 8}, /* EA_MODE_PD7 */
456 { 0, 8, 10}, /* EA_MODE_DI */
457 { 0, 10, 14}, /* EA_MODE_IX */
458 { 0, 8, 10}, /* EA_MODE_AW */
459 { 0, 10, 14}, /* EA_MODE_AL */
460 { 0, 0, 0}, /* EA_MODE_PCDI */
461 { 0, 0, 0}, /* EA_MODE_PCIX */
462 { 0, 0, 0}, /* EA_MODE_I */
467 /* ======================================================================== */
468 /* =========================== UTILITY FUNCTIONS ========================== */
469 /* ======================================================================== */
471 /* Print an error message and exit with status error */
472 void error_exit(char* fmt, ...)
475 fprintf(stderr, "In %s, near or on line %d:\n\t", g_input_filename, g_line_number);
477 vfprintf(stderr, fmt, args);
479 fprintf(stderr, "\n");
481 if(g_prototype_file) fclose(g_prototype_file);
482 if(g_table_file) fclose(g_table_file);
483 if(g_input_file) fclose(g_input_file);
488 /* Print an error message, call perror(), and exit with status error */
489 void perror_exit(char* fmt, ...)
493 vfprintf(stderr, fmt, args);
497 if(g_prototype_file) fclose(g_prototype_file);
498 if(g_table_file) fclose(g_table_file);
499 if(g_input_file) fclose(g_input_file);
505 /* copy until 0 or space and exit with error if we read too far */
506 int check_strsncpy(char* dst, char* src, int maxlength)
509 while(*src && *src != ' ')
512 if(p - dst > maxlength)
513 error_exit("Field too long");
519 /* copy until 0 or specified character and exit with error if we read too far */
520 int check_strcncpy(char* dst, char* src, char delim, int maxlength)
523 while(*src && *src != delim)
526 if(p - dst > maxlength)
527 error_exit("Field too long");
533 /* convert ascii to integer and exit with error if we find invalid data */
534 int check_atoi(char* str, int *result)
538 while(*p >= '0' && *p <= '9')
543 if(*p != ' ' && *p != 0)
544 error_exit("Malformed integer value (%c)", *p);
549 /* Skip past spaces in a string */
550 int skip_spaces(char* str)
560 /* Count the number of set bits in a value */
561 int num_bits(int value)
563 value = ((value & 0xaaaa) >> 1) + (value & 0x5555);
564 value = ((value & 0xcccc) >> 2) + (value & 0x3333);
565 value = ((value & 0xf0f0) >> 4) + (value & 0x0f0f);
566 value = ((value & 0xff00) >> 8) + (value & 0x00ff);
570 /* Convert a hex value written in ASCII */
577 if(*buff >= '0' && *buff <= '9')
580 accum += *buff - '0';
582 else if(*buff >= 'a' && *buff <= 'f')
585 accum += *buff - 'a' + 10;
592 /* Get a line of text from a file, discarding any end-of-line characters */
593 int fgetline(char* buff, int nchars, FILE* file)
597 if(fgets(buff, nchars, file) == NULL)
600 memmove(buff, buff + 1, nchars - 1);
602 length = strlen(buff);
603 while(length && (buff[length-1] == '\r' || buff[length-1] == '\n'))
613 /* ======================================================================== */
614 /* =========================== HELPER FUNCTIONS =========================== */
615 /* ======================================================================== */
617 /* Calculate the number of cycles an opcode requires */
618 int get_oper_cycles(opcode_struct* op, int ea_mode, int cpu_type)
620 int size = g_size_select_table[op->size];
622 if(op->cpus[cpu_type] == '.')
625 if(cpu_type < CPU_TYPE_020)
627 if(cpu_type == CPU_TYPE_010)
629 if(strcmp(op->name, "moves") == 0)
630 return op->cycles[cpu_type] + g_moves_cycle_table[ea_mode][size];
631 if(strcmp(op->name, "clr") == 0)
632 return op->cycles[cpu_type] + g_clr_cycle_table[ea_mode][size];
635 /* ASG: added these cases -- immediate modes take 2 extra cycles here */
636 if(cpu_type == CPU_TYPE_000 && ea_mode == EA_MODE_I &&
637 ((strcmp(op->name, "add") == 0 && strcmp(op->spec_proc, "er") == 0) ||
638 strcmp(op->name, "adda") == 0 ||
639 (strcmp(op->name, "and") == 0 && strcmp(op->spec_proc, "er") == 0) ||
640 (strcmp(op->name, "or") == 0 && strcmp(op->spec_proc, "er") == 0) ||
641 (strcmp(op->name, "sub") == 0 && strcmp(op->spec_proc, "er") == 0) ||
642 strcmp(op->name, "suba") == 0))
643 return op->cycles[cpu_type] + g_ea_cycle_table[ea_mode][cpu_type][size] + 2;
645 if(strcmp(op->name, "jmp") == 0)
646 return op->cycles[cpu_type] + g_jmp_cycle_table[ea_mode];
647 if(strcmp(op->name, "jsr") == 0)
648 return op->cycles[cpu_type] + g_jsr_cycle_table[ea_mode];
649 if(strcmp(op->name, "lea") == 0)
650 return op->cycles[cpu_type] + g_lea_cycle_table[ea_mode];
651 if(strcmp(op->name, "pea") == 0)
652 return op->cycles[cpu_type] + g_pea_cycle_table[ea_mode];
653 if(strcmp(op->name, "movem") == 0)
654 return op->cycles[cpu_type] + g_movem_cycle_table[ea_mode];
656 return op->cycles[cpu_type] + g_ea_cycle_table[ea_mode][cpu_type][size];
659 /* Find an opcode in the opcode handler list */
660 opcode_struct* find_opcode(char* name, int size, char* spec_proc, char* spec_ea)
665 for(op = g_opcode_input_table;op < &g_opcode_input_table[MAX_OPCODE_INPUT_TABLE_LENGTH];op++)
667 if( strcmp(name, op->name) == 0 &&
668 (size == op->size) &&
669 strcmp(spec_proc, op->spec_proc) == 0 &&
670 strcmp(spec_ea, op->spec_ea) == 0)
676 /* Specifically find the illegal opcode in the list */
677 opcode_struct* find_illegal_opcode(void)
681 for(op = g_opcode_input_table;op < &g_opcode_input_table[MAX_OPCODE_INPUT_TABLE_LENGTH];op++)
683 if(strcmp(op->name, "illegal") == 0)
689 /* Parse an opcode handler name */
690 int extract_opcode_info(char* src, char* name, int* size, char* spec_proc, char* spec_ea)
692 char* ptr = strstr(src, ID_OPHANDLER_NAME);
697 ptr += strlen(ID_OPHANDLER_NAME) + 1;
699 ptr += check_strcncpy(name, ptr, ',', MAX_NAME_LENGTH);
700 if(*ptr != ',') return 0;
702 ptr += skip_spaces(ptr);
705 ptr = strstr(ptr, ",");
706 if(ptr == NULL) return 0;
708 ptr += skip_spaces(ptr);
710 ptr += check_strcncpy(spec_proc, ptr, ',', MAX_SPEC_PROC_LENGTH);
711 if(*ptr != ',') return 0;
713 ptr += skip_spaces(ptr);
715 ptr += check_strcncpy(spec_ea, ptr, ')', MAX_SPEC_EA_LENGTH);
716 if(*ptr != ')') return 0;
718 ptr += skip_spaces(ptr);
724 /* Add a search/replace pair to a replace structure */
725 void add_replace_string(replace_struct* replace, char* search_str, char* replace_str)
727 if(replace->length >= MAX_REPLACE_LENGTH)
728 error_exit("overflow in replace structure");
730 strcpy(replace->replace[replace->length][0], search_str);
731 strcpy(replace->replace[replace->length++][1], replace_str);
734 /* Write a function body while replacing any selected strings */
735 void write_body(FILE* filep, body_struct* body, replace_struct* replace)
740 char output[MAX_LINE_LENGTH+1];
741 char temp_buff[MAX_LINE_LENGTH+1];
744 for(i=0;i<body->length;i++)
746 strcpy(output, body->body[i]);
747 /* Check for the base directive header */
748 if(strstr(output, ID_BASE) != NULL)
750 /* Search for any text we need to replace */
752 for(j=0;j<replace->length;j++)
754 ptr = strstr(output, replace->replace[j][0]);
757 /* We found something to replace */
759 strcpy(temp_buff, ptr+strlen(replace->replace[j][0]));
760 strcpy(ptr, replace->replace[j][1]);
761 strcat(ptr, temp_buff);
764 /* Found a directive with no matching replace string */
766 error_exit("Unknown " ID_BASE " directive [%s]", output);
768 fprintf(filep, "%s\n", output);
770 fprintf(filep, "\n\n");
773 /* Generate a base function name from an opcode struct */
774 void get_base_name(char* base_name, opcode_struct* op)
776 sprintf(base_name, "m68k_op_%s", op->name);
778 sprintf(base_name+strlen(base_name), "_%d", op->size);
779 if(strcmp(op->spec_proc, UNSPECIFIED) != 0)
780 sprintf(base_name+strlen(base_name), "_%s", op->spec_proc);
781 if(strcmp(op->spec_ea, UNSPECIFIED) != 0)
782 sprintf(base_name+strlen(base_name), "_%s", op->spec_ea);
785 /* Write the name of an opcode handler function */
786 void write_function_name(FILE* filep, char* base_name)
788 fprintf(filep, "static void %s(void)\n", base_name);
791 void add_opcode_output_table_entry(opcode_struct* op, char* name)
794 if(g_opcode_output_table_length > MAX_OPCODE_OUTPUT_TABLE_LENGTH)
795 error_exit("Opcode output table overflow");
797 ptr = g_opcode_output_table + g_opcode_output_table_length++;
800 strcpy(ptr->name, name);
801 ptr->bits = num_bits(ptr->op_mask);
805 * Comparison function for qsort()
806 * For entries with an equal number of set bits in
807 * the mask compare the match values
809 static int DECL_SPEC compare_nof_true_bits(const void* aptr, const void* bptr)
811 const opcode_struct *a = aptr, *b = bptr;
812 if(a->bits != b->bits)
813 return a->bits - b->bits;
814 if(a->op_mask != b->op_mask)
815 return a->op_mask - b->op_mask;
816 return a->op_match - b->op_match;
819 void print_opcode_output_table(FILE* filep)
822 qsort((void *)g_opcode_output_table, g_opcode_output_table_length, sizeof(g_opcode_output_table[0]), compare_nof_true_bits);
824 for(i=0;i<g_opcode_output_table_length;i++)
825 write_table_entry(filep, g_opcode_output_table+i);
828 /* Write an entry in the opcode handler table */
829 void write_table_entry(FILE* filep, opcode_struct* op)
833 fprintf(filep, "\t{%-28s, 0x%04x, 0x%04x, {",
834 op->name, op->op_mask, op->op_match);
836 for(i=0;i<NUM_CPUS;i++)
838 fprintf(filep, "%3d", op->cycles[i]);
840 fprintf(filep, ", ");
843 fprintf(filep, "}},\n");
846 /* Fill out an opcode struct with a specific addressing mode of the source opcode struct */
847 void set_opcode_struct(opcode_struct* src, opcode_struct* dst, int ea_mode)
853 for(i=0;i<NUM_CPUS;i++)
854 dst->cycles[i] = get_oper_cycles(dst, ea_mode, i);
855 if(strcmp(dst->spec_ea, UNSPECIFIED) == 0 && ea_mode != EA_MODE_NONE)
856 sprintf(dst->spec_ea, "%s", g_ea_info_table[ea_mode].fname_add);
857 dst->op_mask |= g_ea_info_table[ea_mode].mask_add;
858 dst->op_match |= g_ea_info_table[ea_mode].match_add;
862 /* Generate a final opcode handler from the provided data */
863 void generate_opcode_handler(FILE* filep, body_struct* body, replace_struct* replace, opcode_struct* opinfo, int ea_mode)
865 char str[MAX_LINE_LENGTH+1];
866 opcode_struct* op = malloc(sizeof(opcode_struct));
868 /* Set the opcode structure and write the tables, prototypes, etc */
869 set_opcode_struct(opinfo, op, ea_mode);
870 get_base_name(str, op);
871 add_opcode_output_table_entry(op, str);
872 write_function_name(filep, str);
874 /* Add any replace strings needed */
875 if(ea_mode != EA_MODE_NONE)
877 sprintf(str, "EA_%s_8()", g_ea_info_table[ea_mode].ea_add);
878 add_replace_string(replace, ID_OPHANDLER_EA_AY_8, str);
879 sprintf(str, "EA_%s_16()", g_ea_info_table[ea_mode].ea_add);
880 add_replace_string(replace, ID_OPHANDLER_EA_AY_16, str);
881 sprintf(str, "EA_%s_32()", g_ea_info_table[ea_mode].ea_add);
882 add_replace_string(replace, ID_OPHANDLER_EA_AY_32, str);
883 sprintf(str, "OPER_%s_8()", g_ea_info_table[ea_mode].ea_add);
884 add_replace_string(replace, ID_OPHANDLER_OPER_AY_8, str);
885 sprintf(str, "OPER_%s_16()", g_ea_info_table[ea_mode].ea_add);
886 add_replace_string(replace, ID_OPHANDLER_OPER_AY_16, str);
887 sprintf(str, "OPER_%s_32()", g_ea_info_table[ea_mode].ea_add);
888 add_replace_string(replace, ID_OPHANDLER_OPER_AY_32, str);
891 /* Now write the function body with the selected replace strings */
892 write_body(filep, body, replace);
897 /* Generate opcode variants based on available addressing modes */
898 void generate_opcode_ea_variants(FILE* filep, body_struct* body, replace_struct* replace, opcode_struct* op)
900 int old_length = replace->length;
902 /* No ea modes available for this opcode */
903 if(HAS_NO_EA_MODE(op->ea_allowed))
905 generate_opcode_handler(filep, body, replace, op, EA_MODE_NONE);
909 /* Check for and create specific opcodes for each available addressing mode */
910 if(HAS_EA_AI(op->ea_allowed))
911 generate_opcode_handler(filep, body, replace, op, EA_MODE_AI);
912 replace->length = old_length;
913 if(HAS_EA_PI(op->ea_allowed))
915 generate_opcode_handler(filep, body, replace, op, EA_MODE_PI);
916 replace->length = old_length;
918 generate_opcode_handler(filep, body, replace, op, EA_MODE_PI7);
920 replace->length = old_length;
921 if(HAS_EA_PD(op->ea_allowed))
923 generate_opcode_handler(filep, body, replace, op, EA_MODE_PD);
924 replace->length = old_length;
926 generate_opcode_handler(filep, body, replace, op, EA_MODE_PD7);
928 replace->length = old_length;
929 if(HAS_EA_DI(op->ea_allowed))
930 generate_opcode_handler(filep, body, replace, op, EA_MODE_DI);
931 replace->length = old_length;
932 if(HAS_EA_IX(op->ea_allowed))
933 generate_opcode_handler(filep, body, replace, op, EA_MODE_IX);
934 replace->length = old_length;
935 if(HAS_EA_AW(op->ea_allowed))
936 generate_opcode_handler(filep, body, replace, op, EA_MODE_AW);
937 replace->length = old_length;
938 if(HAS_EA_AL(op->ea_allowed))
939 generate_opcode_handler(filep, body, replace, op, EA_MODE_AL);
940 replace->length = old_length;
941 if(HAS_EA_PCDI(op->ea_allowed))
942 generate_opcode_handler(filep, body, replace, op, EA_MODE_PCDI);
943 replace->length = old_length;
944 if(HAS_EA_PCIX(op->ea_allowed))
945 generate_opcode_handler(filep, body, replace, op, EA_MODE_PCIX);
946 replace->length = old_length;
947 if(HAS_EA_I(op->ea_allowed))
948 generate_opcode_handler(filep, body, replace, op, EA_MODE_I);
949 replace->length = old_length;
952 /* Generate variants of condition code opcodes */
953 void generate_opcode_cc_variants(FILE* filep, body_struct* body, replace_struct* replace, opcode_struct* op_in, int offset)
958 int old_length = replace->length;
959 opcode_struct* op = malloc(sizeof(opcode_struct));
963 op->op_mask |= 0x0f00;
965 /* Do all condition codes except t and f */
968 /* Add replace strings for this condition code */
969 sprintf(repl, "COND_%s()", g_cc_table[i][1]);
970 sprintf(replnot, "COND_NOT_%s()", g_cc_table[i][1]);
972 add_replace_string(replace, ID_OPHANDLER_CC, repl);
973 add_replace_string(replace, ID_OPHANDLER_NOT_CC, replnot);
975 /* Set the new opcode info */
976 strcpy(op->name+offset, g_cc_table[i][0]);
978 op->op_match = (op->op_match & 0xf0ff) | (i<<8);
980 /* Generate all opcode variants for this modified opcode */
981 generate_opcode_ea_variants(filep, body, replace, op);
982 /* Remove the above replace strings */
983 replace->length = old_length;
988 /* Process the opcode handlers section of the input file */
989 void process_opcode_handlers(FILE* filep)
991 FILE* input_file = g_input_file;
992 char func_name[MAX_LINE_LENGTH+1];
993 char oper_name[MAX_LINE_LENGTH+1];
995 char oper_spec_proc[MAX_LINE_LENGTH+1];
996 char oper_spec_ea[MAX_LINE_LENGTH+1];
997 opcode_struct* opinfo;
998 replace_struct* replace = malloc(sizeof(replace_struct));
999 body_struct* body = malloc(sizeof(body_struct));
1003 /* Find the first line of the function */
1005 while(strstr(func_name, ID_OPHANDLER_NAME) == NULL)
1007 if(strcmp(func_name, ID_INPUT_SEPARATOR) == 0)
1011 return; /* all done */
1013 if(fgetline(func_name, MAX_LINE_LENGTH, input_file) < 0)
1014 error_exit("Premature end of file when getting function name");
1016 /* Get the rest of the function */
1017 for(body->length=0;;body->length++)
1019 if(body->length > MAX_BODY_LENGTH)
1020 error_exit("Function too long");
1022 if(fgetline(body->body[body->length], MAX_LINE_LENGTH, input_file) < 0)
1023 error_exit("Premature end of file when getting function body");
1025 if(body->body[body->length][0] == '}')
1034 /* Extract the function name information */
1035 if(!extract_opcode_info(func_name, oper_name, &oper_size, oper_spec_proc, oper_spec_ea))
1036 error_exit("Invalid " ID_OPHANDLER_NAME " format");
1038 /* Find the corresponding table entry */
1039 opinfo = find_opcode(oper_name, oper_size, oper_spec_proc, oper_spec_ea);
1041 error_exit("Unable to find matching table entry for %s", func_name);
1043 replace->length = 0;
1045 /* Generate opcode variants */
1046 if(strcmp(opinfo->name, "bcc") == 0 || strcmp(opinfo->name, "scc") == 0)
1047 generate_opcode_cc_variants(filep, body, replace, opinfo, 1);
1048 else if(strcmp(opinfo->name, "dbcc") == 0)
1049 generate_opcode_cc_variants(filep, body, replace, opinfo, 2);
1050 else if(strcmp(opinfo->name, "trapcc") == 0)
1051 generate_opcode_cc_variants(filep, body, replace, opinfo, 4);
1053 generate_opcode_ea_variants(filep, body, replace, opinfo);
1061 /* Populate the opcode handler table from the input file */
1062 void populate_table(void)
1065 char bitpattern[17];
1067 char buff[MAX_LINE_LENGTH];
1073 /* Find the start of the table */
1074 while(strcmp(buff, ID_TABLE_START) != 0)
1075 if(fgetline(buff, MAX_LINE_LENGTH, g_input_file) < 0)
1076 error_exit("(table_start) Premature EOF while reading table");
1078 /* Process the entire table */
1079 for(op = g_opcode_input_table;;op++)
1081 if(fgetline(buff, MAX_LINE_LENGTH, g_input_file) < 0)
1082 error_exit("(inline) Premature EOF while reading table");
1083 if(strlen(buff) == 0)
1085 /* We finish when we find an input separator */
1086 if(strcmp(buff, ID_INPUT_SEPARATOR) == 0)
1089 /* Extract the info from the table */
1093 ptr += skip_spaces(ptr);
1094 ptr += check_strsncpy(op->name, ptr, MAX_NAME_LENGTH);
1097 ptr += skip_spaces(ptr);
1098 ptr += check_atoi(ptr, &temp);
1099 op->size = (unsigned char)temp;
1101 /* Special processing */
1102 ptr += skip_spaces(ptr);
1103 ptr += check_strsncpy(op->spec_proc, ptr, MAX_SPEC_PROC_LENGTH);
1105 /* Specified EA Mode */
1106 ptr += skip_spaces(ptr);
1107 ptr += check_strsncpy(op->spec_ea, ptr, MAX_SPEC_EA_LENGTH);
1109 /* Bit Pattern (more processing later) */
1110 ptr += skip_spaces(ptr);
1111 ptr += check_strsncpy(bitpattern, ptr, 17);
1113 /* Allowed Addressing Mode List */
1114 ptr += skip_spaces(ptr);
1115 ptr += check_strsncpy(op->ea_allowed, ptr, EA_ALLOWED_LENGTH);
1117 /* CPU operating mode (U = user or supervisor, S = supervisor only */
1118 ptr += skip_spaces(ptr);
1119 for(i=0;i<NUM_CPUS;i++)
1121 op->cpu_mode[i] = *ptr++;
1122 ptr += skip_spaces(ptr);
1125 /* Allowed CPUs for this instruction */
1126 for(i=0;i<NUM_CPUS;i++)
1128 ptr += skip_spaces(ptr);
1129 if(*ptr == UNSPECIFIED_CH)
1131 op->cpus[i] = UNSPECIFIED_CH;
1137 op->cpus[i] = '0' + i;
1138 ptr += check_atoi(ptr, &temp);
1139 op->cycles[i] = (unsigned char)temp;
1143 /* generate mask and match from bitpattern */
1148 op->op_mask |= (bitpattern[i] != '.') << (15-i);
1149 op->op_match |= (bitpattern[i] == '1') << (15-i);
1152 /* Terminate the list */
1156 /* Read a header or footer insert from the input file */
1157 void read_insert(char* insert)
1160 char* overflow = insert + MAX_INSERT_LENGTH - MAX_LINE_LENGTH;
1162 char* first_blank = NULL;
1166 /* Skip any leading blank lines */
1167 for(length = 0;length == 0;length = fgetline(ptr, MAX_LINE_LENGTH, g_input_file))
1169 error_exit("Buffer overflow reading inserts");
1171 error_exit("Premature EOF while reading inserts");
1173 /* Advance and append newline */
1175 strcpy(ptr++, "\n");
1177 /* Read until next separator */
1180 /* Read a new line */
1182 error_exit("Buffer overflow reading inserts");
1183 if((length = fgetline(ptr, MAX_LINE_LENGTH, g_input_file)) < 0)
1184 error_exit("Premature EOF while reading inserts");
1186 /* Stop if we read a separator */
1187 if(strcmp(ptr, ID_INPUT_SEPARATOR) == 0)
1190 /* keep track in case there are trailing blanks */
1193 if(first_blank == NULL)
1199 /* Advance and append newline */
1201 strcpy(ptr++, "\n");
1204 /* kill any trailing blank lines */
1212 /* ======================================================================== */
1213 /* ============================= MAIN FUNCTION ============================ */
1214 /* ======================================================================== */
1216 int main(int argc, char **argv)
1219 char output_path[M68K_MAX_DIR] = "";
1220 char filename[M68K_MAX_PATH*2];
1221 /* Section identifier */
1222 char section_id[MAX_LINE_LENGTH+1];
1224 char temp_insert[MAX_INSERT_LENGTH+1];
1225 char prototype_footer_insert[MAX_INSERT_LENGTH+1];
1226 char table_header_insert[MAX_INSERT_LENGTH+1];
1227 char table_footer_insert[MAX_INSERT_LENGTH+1];
1228 char ophandler_header_insert[MAX_INSERT_LENGTH+1];
1229 char ophandler_footer_insert[MAX_INSERT_LENGTH+1];
1230 /* Flags if we've processed certain parts already */
1231 int prototype_header_read = 0;
1232 int prototype_footer_read = 0;
1233 int table_header_read = 0;
1234 int table_footer_read = 0;
1235 int ophandler_header_read = 0;
1236 int ophandler_footer_read = 0;
1237 int table_body_read = 0;
1238 int ophandler_body_read = 0;
1240 printf("\n\tMusashi v%s 68000, 68008, 68010, 68EC020, 68020, 68EC030, 68030, 68EC040, 68040 emulator\n", g_version);
1241 printf("\t\tCopyright Karl Stenerud (kstenerud@gmail.com)\n\n");
1243 /* Check if output path and source for the input file are given */
1247 strcpy(output_path, argv[1]);
1249 for(ptr = strchr(output_path, '\\'); ptr; ptr = strchr(ptr, '\\'))
1251 if(output_path[strlen(output_path)-1] != '/')
1252 strcat(output_path, "/");
1254 strcpy(g_input_filename, argv[2]);
1258 /* Open the files we need */
1259 sprintf(filename, "%s%s", output_path, FILENAME_PROTOTYPE);
1260 if((g_prototype_file = fopen(filename, "wt")) == NULL)
1261 perror_exit("Unable to create prototype file (%s)\n", filename);
1263 sprintf(filename, "%s%s", output_path, FILENAME_TABLE);
1264 if((g_table_file = fopen(filename, "wt")) == NULL)
1265 perror_exit("Unable to create table file (%s)\n", filename);
1267 if((g_input_file=fopen(g_input_filename, "rt")) == NULL)
1268 perror_exit("can't open %s for input", g_input_filename);
1271 /* Get to the first section of the input file */
1273 while(strcmp(section_id, ID_INPUT_SEPARATOR) != 0)
1274 if(fgetline(section_id, MAX_LINE_LENGTH, g_input_file) < 0)
1275 error_exit("Premature EOF while reading input file");
1277 /* Now process all sections */
1280 if(fgetline(section_id, MAX_LINE_LENGTH, g_input_file) < 0)
1281 error_exit("Premature EOF while reading input file");
1282 if(strcmp(section_id, ID_PROTOTYPE_HEADER) == 0)
1284 if(prototype_header_read)
1285 error_exit("Duplicate prototype header");
1286 read_insert(temp_insert);
1287 fprintf(g_prototype_file, "%s\n\n", temp_insert);
1288 prototype_header_read = 1;
1290 else if(strcmp(section_id, ID_TABLE_HEADER) == 0)
1292 if(table_header_read)
1293 error_exit("Duplicate table header");
1294 read_insert(table_header_insert);
1295 table_header_read = 1;
1297 else if(strcmp(section_id, ID_OPHANDLER_HEADER) == 0)
1299 if(ophandler_header_read)
1300 error_exit("Duplicate opcode handler header");
1301 read_insert(ophandler_header_insert);
1302 ophandler_header_read = 1;
1304 else if(strcmp(section_id, ID_PROTOTYPE_FOOTER) == 0)
1306 if(prototype_footer_read)
1307 error_exit("Duplicate prototype footer");
1308 read_insert(prototype_footer_insert);
1309 prototype_footer_read = 1;
1311 else if(strcmp(section_id, ID_TABLE_FOOTER) == 0)
1313 if(table_footer_read)
1314 error_exit("Duplicate table footer");
1315 read_insert(table_footer_insert);
1316 table_footer_read = 1;
1318 else if(strcmp(section_id, ID_OPHANDLER_FOOTER) == 0)
1320 if(ophandler_footer_read)
1321 error_exit("Duplicate opcode handler footer");
1322 read_insert(ophandler_footer_insert);
1323 ophandler_footer_read = 1;
1325 else if(strcmp(section_id, ID_TABLE_BODY) == 0)
1327 if(!prototype_header_read)
1328 error_exit("Table body encountered before prototype header");
1329 if(!table_header_read)
1330 error_exit("Table body encountered before table header");
1331 if(!ophandler_header_read)
1332 error_exit("Table body encountered before opcode handler header");
1335 error_exit("Duplicate table body");
1338 table_body_read = 1;
1340 else if(strcmp(section_id, ID_OPHANDLER_BODY) == 0)
1342 if(!prototype_header_read)
1343 error_exit("Opcode handlers encountered before prototype header");
1344 if(!table_header_read)
1345 error_exit("Opcode handlers encountered before table header");
1346 if(!ophandler_header_read)
1347 error_exit("Opcode handlers encountered before opcode handler header");
1348 if(!table_body_read)
1349 error_exit("Opcode handlers encountered before table body");
1351 if(ophandler_body_read)
1352 error_exit("Duplicate opcode handler section");
1354 fprintf(g_table_file, "%s\n\n", ophandler_header_insert);
1355 process_opcode_handlers(g_table_file);
1356 fprintf(g_table_file, "%s\n\n", ophandler_footer_insert);
1358 ophandler_body_read = 1;
1360 else if(strcmp(section_id, ID_END) == 0)
1362 /* End of input file. Do a sanity check and then write footers */
1363 if(!prototype_header_read)
1364 error_exit("Missing prototype header");
1365 if(!prototype_footer_read)
1366 error_exit("Missing prototype footer");
1367 if(!table_header_read)
1368 error_exit("Missing table header");
1369 if(!table_footer_read)
1370 error_exit("Missing table footer");
1371 if(!table_body_read)
1372 error_exit("Missing table body");
1373 if(!ophandler_header_read)
1374 error_exit("Missing opcode handler header");
1375 if(!ophandler_footer_read)
1376 error_exit("Missing opcode handler footer");
1377 if(!ophandler_body_read)
1378 error_exit("Missing opcode handler body");
1380 fprintf(g_table_file, "%s\n\n", table_header_insert);
1381 print_opcode_output_table(g_table_file);
1382 fprintf(g_table_file, "%s\n\n", table_footer_insert);
1384 fprintf(g_prototype_file, "%s\n\n", prototype_footer_insert);
1390 error_exit("Unknown section identifier: %s", section_id);
1394 /* Close all files and exit */
1395 fclose(g_prototype_file);
1396 fclose(g_table_file);
1397 fclose(g_input_file);
1399 printf("Generated %d opcode handlers from %d primitives\n", g_num_functions, g_num_primitives);
1406 /* ======================================================================== */
1407 /* ============================== END OF FILE ============================= */
1408 /* ======================================================================== */