1 /* ======================================================================== */
2 /* ========================= LICENSING & COPYRIGHT ======================== */
3 /* ======================================================================== */
8 * A portable Motorola M680x0 processor emulation engine.
9 * Copyright 1998-2001 Karl Stenerud. All rights reserved.
11 * This code may be freely used for non-commercial purposes as long as this
12 * copyright notice remains unaltered in the source code and any binary files
13 * containing this code in compiled form.
15 * All other lisencing terms must be negotiated with the author
18 * The latest version of this code can be obtained at:
19 * http://kstenerud.cjb.net
24 /* ======================================================================== */
25 /* ============================ CODE GENERATOR ============================ */
26 /* ======================================================================== */
28 * This is the code generator program which will generate the opcode table
29 * and the final opcode handlers.
31 * It requires an input file to function (default m68k_in.c), but you can
32 * specify your own like so:
34 * m68kmake <output path> <input file>
36 * where output path is the path where the output files should be placed, and
37 * input file is the file to use for input.
39 * If you modify the input file greatly from its released form, you may have
40 * to tweak the configuration section a bit since I'm using static allocation
41 * to keep things simple.
44 * TODO: - build a better code generator for the move instruction.
45 * - Add callm and rtm instructions
46 * - Fix RTE to handle other format words
47 * - Add address error (and bus error?) handling
51 char* g_version = "3.3";
53 /* ======================================================================== */
54 /* =============================== INCLUDES =============================== */
55 /* ======================================================================== */
65 /* ======================================================================== */
66 /* ============================= CONFIGURATION ============================ */
67 /* ======================================================================== */
72 #define NUM_CPUS 3 /* 000, 010, 020 */
73 #define MAX_LINE_LENGTH 200 /* length of 1 line */
74 #define MAX_BODY_LENGTH 300 /* Number of lines in 1 function */
75 #define MAX_REPLACE_LENGTH 30 /* Max number of replace strings */
76 #define MAX_INSERT_LENGTH 5000 /* Max size of insert piece */
77 #define MAX_NAME_LENGTH 30 /* Max length of ophandler name */
78 #define MAX_SPEC_PROC_LENGTH 4 /* Max length of special processing str */
79 #define MAX_SPEC_EA_LENGTH 5 /* Max length of specified EA str */
80 #define EA_ALLOWED_LENGTH 11 /* Max length of ea allowed str */
81 #define MAX_OPCODE_INPUT_TABLE_LENGTH 1000 /* Max length of opcode handler tbl */
82 #define MAX_OPCODE_OUTPUT_TABLE_LENGTH 3000 /* Max length of opcode handler tbl */
84 /* Default filenames */
85 #define FILENAME_INPUT "m68k_in.c"
86 #define FILENAME_PROTOTYPE "m68kops.h"
87 #define FILENAME_TABLE "m68kops.c"
88 #define FILENAME_OPS_AC "m68kopac.c"
89 #define FILENAME_OPS_DM "m68kopdm.c"
90 #define FILENAME_OPS_NZ "m68kopnz.c"
93 /* Identifier sequences recognized by this program */
95 #define ID_INPUT_SEPARATOR "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
97 #define ID_BASE "M68KMAKE"
98 #define ID_PROTOTYPE_HEADER ID_BASE "_PROTOTYPE_HEADER"
99 #define ID_PROTOTYPE_FOOTER ID_BASE "_PROTOTYPE_FOOTER"
100 #define ID_TABLE_HEADER ID_BASE "_TABLE_HEADER"
101 #define ID_TABLE_FOOTER ID_BASE "_TABLE_FOOTER"
102 #define ID_TABLE_BODY ID_BASE "_TABLE_BODY"
103 #define ID_TABLE_START ID_BASE "_TABLE_START"
104 #define ID_OPHANDLER_HEADER ID_BASE "_OPCODE_HANDLER_HEADER"
105 #define ID_OPHANDLER_FOOTER ID_BASE "_OPCODE_HANDLER_FOOTER"
106 #define ID_OPHANDLER_BODY ID_BASE "_OPCODE_HANDLER_BODY"
107 #define ID_END ID_BASE "_END"
109 #define ID_OPHANDLER_NAME ID_BASE "_OP"
110 #define ID_OPHANDLER_EA_AY_8 ID_BASE "_GET_EA_AY_8"
111 #define ID_OPHANDLER_EA_AY_16 ID_BASE "_GET_EA_AY_16"
112 #define ID_OPHANDLER_EA_AY_32 ID_BASE "_GET_EA_AY_32"
113 #define ID_OPHANDLER_OPER_AY_8 ID_BASE "_GET_OPER_AY_8"
114 #define ID_OPHANDLER_OPER_AY_16 ID_BASE "_GET_OPER_AY_16"
115 #define ID_OPHANDLER_OPER_AY_32 ID_BASE "_GET_OPER_AY_32"
116 #define ID_OPHANDLER_CC ID_BASE "_CC"
117 #define ID_OPHANDLER_NOT_CC ID_BASE "_NOT_CC"
122 #endif /* DECL_SPEC */
126 /* ======================================================================== */
127 /* ============================== PROTOTYPES ============================== */
128 /* ======================================================================== */
130 #define CPU_TYPE_000 0
131 #define CPU_TYPE_010 1
132 #define CPU_TYPE_020 2
134 #define UNSPECIFIED "."
135 #define UNSPECIFIED_CH '.'
137 #define HAS_NO_EA_MODE(A) (strcmp(A, "..........") == 0)
138 #define HAS_EA_AI(A) ((A)[0] == 'A')
139 #define HAS_EA_PI(A) ((A)[1] == '+')
140 #define HAS_EA_PD(A) ((A)[2] == '-')
141 #define HAS_EA_DI(A) ((A)[3] == 'D')
142 #define HAS_EA_IX(A) ((A)[4] == 'X')
143 #define HAS_EA_AW(A) ((A)[5] == 'W')
144 #define HAS_EA_AL(A) ((A)[6] == 'L')
145 #define HAS_EA_PCDI(A) ((A)[7] == 'd')
146 #define HAS_EA_PCIX(A) ((A)[8] == 'x')
147 #define HAS_EA_I(A) ((A)[9] == 'I')
151 EA_MODE_NONE, /* No special addressing mode */
152 EA_MODE_AI, /* Address register indirect */
153 EA_MODE_PI, /* Address register indirect with postincrement */
154 EA_MODE_PI7, /* Address register 7 indirect with postincrement */
155 EA_MODE_PD, /* Address register indirect with predecrement */
156 EA_MODE_PD7, /* Address register 7 indirect with predecrement */
157 EA_MODE_DI, /* Address register indirect with displacement */
158 EA_MODE_IX, /* Address register indirect with index */
159 EA_MODE_AW, /* Absolute word */
160 EA_MODE_AL, /* Absolute long */
161 EA_MODE_PCDI, /* Program counter indirect with displacement */
162 EA_MODE_PCIX, /* Program counter indirect with index */
163 EA_MODE_I /* Immediate */
167 /* Everything we need to know about an opcode */
170 char name[MAX_NAME_LENGTH]; /* opcode handler name */
171 unsigned int size; /* Size of operation */
172 char spec_proc[MAX_SPEC_PROC_LENGTH]; /* Special processing mode */
173 char spec_ea[MAX_SPEC_EA_LENGTH]; /* Specified effective addressing mode */
174 unsigned int bits; /* Number of significant bits (used for sorting the table) */
175 unsigned int op_mask; /* Mask to apply for matching an opcode to a handler */
176 unsigned int op_match; /* Value to match after masking */
177 char ea_allowed[EA_ALLOWED_LENGTH]; /* Effective addressing modes allowed */
178 char cpu_mode[NUM_CPUS]; /* User or supervisor mode */
179 char cpus[NUM_CPUS+1]; /* Allowed CPUs */
180 unsigned int cycles[NUM_CPUS]; /* cycles for 000, 010, 020 */
184 /* All modifications necessary for a specific EA mode of an instruction */
189 unsigned int mask_add;
190 unsigned int match_add;
194 /* Holds the body of a function */
197 char body[MAX_BODY_LENGTH][MAX_LINE_LENGTH+1];
202 /* Holds a sequence of search / replace strings */
205 char replace[MAX_REPLACE_LENGTH][2][MAX_LINE_LENGTH+1];
210 /* Function Prototypes */
211 void error_exit(char* fmt, ...);
212 void perror_exit(char* fmt, ...);
213 int check_strsncpy(char* dst, char* src, int maxlength);
214 int check_atoi(char* str, int *result);
215 int skip_spaces(char* str);
216 int num_bits(int value);
217 int atoh(char* buff);
218 int fgetline(char* buff, int nchars, FILE* file);
219 int get_oper_cycles(opcode_struct* op, int ea_mode, int cpu_type);
220 opcode_struct* find_opcode(char* name, int size, char* spec_proc, char* spec_ea);
221 opcode_struct* find_illegal_opcode(void);
222 int extract_opcode_info(char* src, char* name, int* size, char* spec_proc, char* spec_ea);
223 void add_replace_string(replace_struct* replace, char* search_str, char* replace_str);
224 void write_body(FILE* filep, body_struct* body, replace_struct* replace);
225 void get_base_name(char* base_name, opcode_struct* op);
226 void write_prototype(FILE* filep, char* base_name);
227 void write_function_name(FILE* filep, char* base_name);
228 void add_opcode_output_table_entry(opcode_struct* op, char* name);
229 static int DECL_SPEC compare_nof_true_bits(const void* aptr, const void* bptr);
230 void print_opcode_output_table(FILE* filep);
231 void write_table_entry(FILE* filep, opcode_struct* op);
232 void set_opcode_struct(opcode_struct* src, opcode_struct* dst, int ea_mode);
233 void generate_opcode_handler(FILE* filep, body_struct* body, replace_struct* replace, opcode_struct* opinfo, int ea_mode);
234 void generate_opcode_ea_variants(FILE* filep, body_struct* body, replace_struct* replace, opcode_struct* op);
235 void generate_opcode_cc_variants(FILE* filep, body_struct* body, replace_struct* replace, opcode_struct* op_in, int offset);
236 void process_opcode_handlers(void);
237 void populate_table(void);
238 void read_insert(char* insert);
242 /* ======================================================================== */
243 /* ================================= DATA ================================= */
244 /* ======================================================================== */
246 /* Name of the input file */
247 char g_input_filename[MAX_PATH] = FILENAME_INPUT;
250 FILE* g_input_file = NULL;
251 FILE* g_prototype_file = NULL;
252 FILE* g_table_file = NULL;
253 FILE* g_ops_ac_file = NULL;
254 FILE* g_ops_dm_file = NULL;
255 FILE* g_ops_nz_file = NULL;
257 int g_num_functions = 0; /* Number of functions processed */
258 int g_num_primitives = 0; /* Number of function primitives read */
259 int g_line_number = 1; /* Current line number */
261 /* Opcode handler table */
262 opcode_struct g_opcode_input_table[MAX_OPCODE_INPUT_TABLE_LENGTH];
264 opcode_struct g_opcode_output_table[MAX_OPCODE_OUTPUT_TABLE_LENGTH];
265 int g_opcode_output_table_length = 0;
267 ea_info_struct g_ea_info_table[13] =
268 {/* fname ea mask match */
269 {"", "", 0x00, 0x00}, /* EA_MODE_NONE */
270 {"ai", "AY_AI", 0x38, 0x10}, /* EA_MODE_AI */
271 {"pi", "AY_PI", 0x38, 0x18}, /* EA_MODE_PI */
272 {"pi7", "A7_PI", 0x3f, 0x1f}, /* EA_MODE_PI7 */
273 {"pd", "AY_PD", 0x38, 0x20}, /* EA_MODE_PD */
274 {"pd7", "A7_PD", 0x3f, 0x27}, /* EA_MODE_PD7 */
275 {"di", "AY_DI", 0x38, 0x28}, /* EA_MODE_DI */
276 {"ix", "AY_IX", 0x38, 0x30}, /* EA_MODE_IX */
277 {"aw", "AW", 0x3f, 0x38}, /* EA_MODE_AW */
278 {"al", "AL", 0x3f, 0x39}, /* EA_MODE_AL */
279 {"pcdi", "PCDI", 0x3f, 0x3a}, /* EA_MODE_PCDI */
280 {"pcix", "PCIX", 0x3f, 0x3b}, /* EA_MODE_PCIX */
281 {"i", "I", 0x3f, 0x3c}, /* EA_MODE_I */
285 char* g_cc_table[16][2] =
287 { "t", "T"}, /* 0000 */
288 { "f", "F"}, /* 0001 */
289 {"hi", "HI"}, /* 0010 */
290 {"ls", "LS"}, /* 0011 */
291 {"cc", "CC"}, /* 0100 */
292 {"cs", "CS"}, /* 0101 */
293 {"ne", "NE"}, /* 0110 */
294 {"eq", "EQ"}, /* 0111 */
295 {"vc", "VC"}, /* 1000 */
296 {"vs", "VS"}, /* 1001 */
297 {"pl", "PL"}, /* 1010 */
298 {"mi", "MI"}, /* 1011 */
299 {"ge", "GE"}, /* 1100 */
300 {"lt", "LT"}, /* 1101 */
301 {"gt", "GT"}, /* 1110 */
302 {"le", "LE"}, /* 1111 */
305 /* size to index translator (0 -> 0, 8 and 16 -> 1, 32 -> 2) */
306 int g_size_select_table[33] =
309 0, 0, 0, 0, 0, 0, 0, 1, /* 8 */
310 0, 0, 0, 0, 0, 0, 0, 1, /* 16 */
311 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 /* 32 */
314 /* Extra cycles required for certain EA modes */
315 int g_ea_cycle_table[13][NUM_CPUS][3] =
317 {{ 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}}, /* EA_MODE_NONE */
318 {{ 0, 4, 8}, { 0, 4, 8}, { 0, 4, 4}}, /* EA_MODE_AI */
319 {{ 0, 4, 8}, { 0, 4, 8}, { 0, 4, 4}}, /* EA_MODE_PI */
320 {{ 0, 4, 8}, { 0, 4, 8}, { 0, 4, 4}}, /* EA_MODE_PI7 */
321 {{ 0, 6, 10}, { 0, 6, 10}, { 0, 5, 5}}, /* EA_MODE_PD */
322 {{ 0, 6, 10}, { 0, 6, 10}, { 0, 5, 5}}, /* EA_MODE_PD7 */
323 {{ 0, 8, 12}, { 0, 8, 12}, { 0, 5, 5}}, /* EA_MODE_DI */
324 {{ 0, 10, 14}, { 0, 10, 14}, { 0, 7, 7}}, /* EA_MODE_IX */
325 {{ 0, 8, 12}, { 0, 8, 12}, { 0, 4, 4}}, /* EA_MODE_AW */
326 {{ 0, 12, 16}, { 0, 12, 16}, { 0, 4, 4}}, /* EA_MODE_AL */
327 {{ 0, 8, 12}, { 0, 8, 12}, { 0, 5, 5}}, /* EA_MODE_PCDI */
328 {{ 0, 10, 14}, { 0, 10, 14}, { 0, 7, 7}}, /* EA_MODE_PCIX */
329 {{ 0, 4, 8}, { 0, 4, 8}, { 0, 2, 4}}, /* EA_MODE_I */
332 /* Extra cycles for JMP instruction (000, 010) */
333 int g_jmp_cycle_table[13] =
335 0, /* EA_MODE_NONE */
345 6, /* EA_MODE_PCDI */
346 10, /* EA_MODE_PCIX */
350 /* Extra cycles for JSR instruction (000, 010) */
351 int g_jsr_cycle_table[13] =
353 0, /* EA_MODE_NONE */
363 6, /* EA_MODE_PCDI */
364 10, /* EA_MODE_PCIX */
368 /* Extra cycles for LEA instruction (000, 010) */
369 int g_lea_cycle_table[13] =
371 0, /* EA_MODE_NONE */
381 8, /* EA_MODE_PCDI */
382 12, /* EA_MODE_PCIX */
386 /* Extra cycles for PEA instruction (000, 010) */
387 int g_pea_cycle_table[13] =
389 0, /* EA_MODE_NONE */
399 10, /* EA_MODE_PCDI */
400 14, /* EA_MODE_PCIX */
404 /* Extra cycles for MOVES instruction (010) */
405 int g_moves_cycle_table[13][3] =
407 { 0, 0, 0}, /* EA_MODE_NONE */
408 { 0, 4, 6}, /* EA_MODE_AI */
409 { 0, 4, 6}, /* EA_MODE_PI */
410 { 0, 4, 6}, /* EA_MODE_PI7 */
411 { 0, 6, 12}, /* EA_MODE_PD */
412 { 0, 6, 12}, /* EA_MODE_PD7 */
413 { 0, 12, 16}, /* EA_MODE_DI */
414 { 0, 16, 20}, /* EA_MODE_IX */
415 { 0, 12, 16}, /* EA_MODE_AW */
416 { 0, 16, 20}, /* EA_MODE_AL */
417 { 0, 0, 0}, /* EA_MODE_PCDI */
418 { 0, 0, 0}, /* EA_MODE_PCIX */
419 { 0, 0, 0}, /* EA_MODE_I */
422 /* Extra cycles for CLR instruction (010) */
423 int g_clr_cycle_table[13][3] =
425 { 0, 0, 0}, /* EA_MODE_NONE */
426 { 0, 4, 6}, /* EA_MODE_AI */
427 { 0, 4, 6}, /* EA_MODE_PI */
428 { 0, 4, 6}, /* EA_MODE_PI7 */
429 { 0, 6, 8}, /* EA_MODE_PD */
430 { 0, 6, 8}, /* EA_MODE_PD7 */
431 { 0, 8, 10}, /* EA_MODE_DI */
432 { 0, 10, 14}, /* EA_MODE_IX */
433 { 0, 8, 10}, /* EA_MODE_AW */
434 { 0, 10, 14}, /* EA_MODE_AL */
435 { 0, 0, 0}, /* EA_MODE_PCDI */
436 { 0, 0, 0}, /* EA_MODE_PCIX */
437 { 0, 0, 0}, /* EA_MODE_I */
442 /* ======================================================================== */
443 /* =========================== UTILITY FUNCTIONS ========================== */
444 /* ======================================================================== */
446 /* Print an error message and exit with status error */
447 void error_exit(char* fmt, ...)
450 fprintf(stderr, "In %s, near or on line %d:\n\t", g_input_filename, g_line_number);
452 vfprintf(stderr, fmt, args);
454 fprintf(stderr, "\n");
456 if(g_prototype_file) fclose(g_prototype_file);
457 if(g_table_file) fclose(g_table_file);
458 if(g_ops_ac_file) fclose(g_ops_ac_file);
459 if(g_ops_dm_file) fclose(g_ops_dm_file);
460 if(g_ops_nz_file) fclose(g_ops_nz_file);
461 if(g_input_file) fclose(g_input_file);
466 /* Print an error message, call perror(), and exit with status error */
467 void perror_exit(char* fmt, ...)
471 vfprintf(stderr, fmt, args);
475 if(g_prototype_file) fclose(g_prototype_file);
476 if(g_table_file) fclose(g_table_file);
477 if(g_ops_ac_file) fclose(g_ops_ac_file);
478 if(g_ops_dm_file) fclose(g_ops_dm_file);
479 if(g_ops_nz_file) fclose(g_ops_nz_file);
480 if(g_input_file) fclose(g_input_file);
486 /* copy until 0 or space and exit with error if we read too far */
487 int check_strsncpy(char* dst, char* src, int maxlength)
490 while(*src && *src != ' ')
493 if(p - dst > maxlength)
494 error_exit("Field too long");
500 /* copy until 0 or specified character and exit with error if we read too far */
501 int check_strcncpy(char* dst, char* src, char delim, int maxlength)
504 while(*src && *src != delim)
507 if(p - dst > maxlength)
508 error_exit("Field too long");
514 /* convert ascii to integer and exit with error if we find invalid data */
515 int check_atoi(char* str, int *result)
519 while(*p >= '0' && *p <= '9')
524 if(*p != ' ' && *p != 0)
525 error_exit("Malformed integer value (%c)", *p);
530 /* Skip past spaces in a string */
531 int skip_spaces(char* str)
541 /* Count the number of set bits in a value */
542 int num_bits(int value)
544 value = ((value & 0xaaaa) >> 1) + (value & 0x5555);
545 value = ((value & 0xcccc) >> 2) + (value & 0x3333);
546 value = ((value & 0xf0f0) >> 4) + (value & 0x0f0f);
547 value = ((value & 0xff00) >> 8) + (value & 0x00ff);
551 /* Convert a hex value written in ASCII */
558 if(*buff >= '0' && *buff <= '9')
561 accum += *buff - '0';
563 else if(*buff >= 'a' && *buff <= 'f')
566 accum += *buff - 'a' + 10;
573 /* Get a line of text from a file, discarding any end-of-line characters */
574 int fgetline(char* buff, int nchars, FILE* file)
578 if(fgets(buff, nchars, file) == NULL)
581 memcpy(buff, buff + 1, nchars - 1);
583 length = strlen(buff);
584 while(length && (buff[length-1] == '\r' || buff[length-1] == '\n'))
594 /* ======================================================================== */
595 /* =========================== HELPER FUNCTIONS =========================== */
596 /* ======================================================================== */
598 /* Calculate the number of cycles an opcode requires */
599 int get_oper_cycles(opcode_struct* op, int ea_mode, int cpu_type)
601 int size = g_size_select_table[op->size];
603 if(op->cpus[cpu_type] == '.')
606 if(cpu_type < CPU_TYPE_020)
608 if(cpu_type == CPU_TYPE_010)
610 if(strcmp(op->name, "moves") == 0)
611 return op->cycles[cpu_type] + g_moves_cycle_table[ea_mode][size];
612 if(strcmp(op->name, "clr") == 0)
613 return op->cycles[cpu_type] + g_clr_cycle_table[ea_mode][size];
616 /* ASG: added these cases -- immediate modes take 2 extra cycles here */
617 if(cpu_type == CPU_TYPE_000 && ea_mode == EA_MODE_I &&
618 ((strcmp(op->name, "add") == 0 && strcmp(op->spec_proc, "er") == 0) ||
619 strcmp(op->name, "adda") == 0 ||
620 (strcmp(op->name, "and") == 0 && strcmp(op->spec_proc, "er") == 0) ||
621 (strcmp(op->name, "or") == 0 && strcmp(op->spec_proc, "er") == 0) ||
622 (strcmp(op->name, "sub") == 0 && strcmp(op->spec_proc, "er") == 0) ||
623 strcmp(op->name, "suba") == 0))
624 return op->cycles[cpu_type] + g_ea_cycle_table[ea_mode][cpu_type][size] + 2;
626 if(strcmp(op->name, "jmp") == 0)
627 return op->cycles[cpu_type] + g_jmp_cycle_table[ea_mode];
628 if(strcmp(op->name, "jsr") == 0)
629 return op->cycles[cpu_type] + g_jsr_cycle_table[ea_mode];
630 if(strcmp(op->name, "lea") == 0)
631 return op->cycles[cpu_type] + g_lea_cycle_table[ea_mode];
632 if(strcmp(op->name, "pea") == 0)
633 return op->cycles[cpu_type] + g_pea_cycle_table[ea_mode];
635 return op->cycles[cpu_type] + g_ea_cycle_table[ea_mode][cpu_type][size];
638 /* Find an opcode in the opcode handler list */
639 opcode_struct* find_opcode(char* name, int size, char* spec_proc, char* spec_ea)
644 for(op = g_opcode_input_table;op->name != NULL;op++)
646 if( strcmp(name, op->name) == 0 &&
647 (size == (int)op->size) &&
648 strcmp(spec_proc, op->spec_proc) == 0 &&
649 strcmp(spec_ea, op->spec_ea) == 0)
655 /* Specifically find the illegal opcode in the list */
656 opcode_struct* find_illegal_opcode(void)
660 for(op = g_opcode_input_table;op->name != NULL;op++)
662 if(strcmp(op->name, "illegal") == 0)
668 /* Parse an opcode handler name */
669 int extract_opcode_info(char* src, char* name, int* size, char* spec_proc, char* spec_ea)
671 char* ptr = strstr(src, ID_OPHANDLER_NAME);
676 ptr += strlen(ID_OPHANDLER_NAME) + 1;
678 ptr += check_strcncpy(name, ptr, ',', MAX_NAME_LENGTH);
679 if(*ptr != ',') return 0;
681 ptr += skip_spaces(ptr);
684 ptr = strstr(ptr, ",");
685 if(ptr == NULL) return 0;
687 ptr += skip_spaces(ptr);
689 ptr += check_strcncpy(spec_proc, ptr, ',', MAX_SPEC_PROC_LENGTH);
690 if(*ptr != ',') return 0;
692 ptr += skip_spaces(ptr);
694 ptr += check_strcncpy(spec_ea, ptr, ')', MAX_SPEC_EA_LENGTH);
695 if(*ptr != ')') return 0;
701 /* Add a search/replace pair to a replace structure */
702 void add_replace_string(replace_struct* replace, char* search_str, char* replace_str)
704 if(replace->length >= MAX_REPLACE_LENGTH)
705 error_exit("overflow in replace structure");
707 strcpy(replace->replace[replace->length][0], search_str);
708 strcpy(replace->replace[replace->length++][1], replace_str);
711 /* Write a function body while replacing any selected strings */
712 void write_body(FILE* filep, body_struct* body, replace_struct* replace)
717 char output[MAX_LINE_LENGTH+1];
718 char temp_buff[MAX_LINE_LENGTH+1];
721 for(i=0;i<body->length;i++)
723 strcpy(output, body->body[i]);
724 /* Check for the base directive header */
725 if(strstr(output, ID_BASE) != NULL)
727 /* Search for any text we need to replace */
729 for(j=0;j<replace->length;j++)
731 ptr = strstr(output, replace->replace[j][0]);
734 /* We found something to replace */
736 strcpy(temp_buff, ptr+strlen(replace->replace[j][0]));
737 strcpy(ptr, replace->replace[j][1]);
738 strcat(ptr, temp_buff);
741 /* Found a directive with no matching replace string */
743 error_exit("Unknown " ID_BASE " directive");
745 fprintf(filep, "%s\n", output);
747 fprintf(filep, "\n\n");
750 /* Generate a base function name from an opcode struct */
751 void get_base_name(char* base_name, opcode_struct* op)
753 sprintf(base_name, "m68k_op_%s", op->name);
755 sprintf(base_name+strlen(base_name), "_%d", op->size);
756 if(strcmp(op->spec_proc, UNSPECIFIED) != 0)
757 sprintf(base_name+strlen(base_name), "_%s", op->spec_proc);
758 if(strcmp(op->spec_ea, UNSPECIFIED) != 0)
759 sprintf(base_name+strlen(base_name), "_%s", op->spec_ea);
762 /* Write the prototype of an opcode handler function */
763 void write_prototype(FILE* filep, char* base_name)
765 fprintf(filep, "void %s(void);\n", base_name);
768 /* Write the name of an opcode handler function */
769 void write_function_name(FILE* filep, char* base_name)
771 fprintf(filep, "void %s(void)\n", base_name);
774 void add_opcode_output_table_entry(opcode_struct* op, char* name)
777 if(g_opcode_output_table_length > MAX_OPCODE_OUTPUT_TABLE_LENGTH)
778 error_exit("Opcode output table overflow");
780 ptr = g_opcode_output_table + g_opcode_output_table_length++;
783 strcpy(ptr->name, name);
784 ptr->bits = num_bits(ptr->op_mask);
788 * Comparison function for qsort()
789 * For entries with an equal number of set bits in
790 * the mask compare the match values
792 static int DECL_SPEC compare_nof_true_bits(const void* aptr, const void* bptr)
794 const opcode_struct *a = aptr, *b = bptr;
795 if(a->bits != b->bits)
796 return a->bits - b->bits;
797 if(a->op_mask != b->op_mask)
798 return a->op_mask - b->op_mask;
799 return a->op_match - b->op_match;
802 void print_opcode_output_table(FILE* filep)
805 qsort((void *)g_opcode_output_table, g_opcode_output_table_length, sizeof(g_opcode_output_table[0]), compare_nof_true_bits);
807 for(i=0;i<g_opcode_output_table_length;i++)
808 write_table_entry(filep, g_opcode_output_table+i);
811 /* Write an entry in the opcode handler table */
812 void write_table_entry(FILE* filep, opcode_struct* op)
816 fprintf(filep, "\t{%-28s, 0x%04x, 0x%04x, {",
817 op->name, op->op_mask, op->op_match);
819 for(i=0;i<NUM_CPUS;i++)
821 fprintf(filep, "%3d", op->cycles[i]);
823 fprintf(filep, ", ");
826 fprintf(filep, "}},\n");
829 /* Fill out an opcode struct with a specific addressing mode of the source opcode struct */
830 void set_opcode_struct(opcode_struct* src, opcode_struct* dst, int ea_mode)
836 for(i=0;i<NUM_CPUS;i++)
837 dst->cycles[i] = get_oper_cycles(dst, ea_mode, i);
838 if(strcmp(dst->spec_ea, UNSPECIFIED) == 0 && ea_mode != EA_MODE_NONE)
839 sprintf(dst->spec_ea, "%s", g_ea_info_table[ea_mode].fname_add);
840 dst->op_mask |= g_ea_info_table[ea_mode].mask_add;
841 dst->op_match |= g_ea_info_table[ea_mode].match_add;
845 /* Generate a final opcode handler from the provided data */
846 void generate_opcode_handler(FILE* filep, body_struct* body, replace_struct* replace, opcode_struct* opinfo, int ea_mode)
848 char str[MAX_LINE_LENGTH+1];
849 opcode_struct* op = malloc(sizeof(opcode_struct));
851 /* Set the opcode structure and write the tables, prototypes, etc */
852 set_opcode_struct(opinfo, op, ea_mode);
853 get_base_name(str, op);
854 write_prototype(g_prototype_file, str);
855 add_opcode_output_table_entry(op, str);
856 write_function_name(filep, str);
858 /* Add any replace strings needed */
859 if(ea_mode != EA_MODE_NONE)
861 sprintf(str, "EA_%s_8()", g_ea_info_table[ea_mode].ea_add);
862 add_replace_string(replace, ID_OPHANDLER_EA_AY_8, str);
863 sprintf(str, "EA_%s_16()", g_ea_info_table[ea_mode].ea_add);
864 add_replace_string(replace, ID_OPHANDLER_EA_AY_16, str);
865 sprintf(str, "EA_%s_32()", g_ea_info_table[ea_mode].ea_add);
866 add_replace_string(replace, ID_OPHANDLER_EA_AY_32, str);
867 sprintf(str, "OPER_%s_8()", g_ea_info_table[ea_mode].ea_add);
868 add_replace_string(replace, ID_OPHANDLER_OPER_AY_8, str);
869 sprintf(str, "OPER_%s_16()", g_ea_info_table[ea_mode].ea_add);
870 add_replace_string(replace, ID_OPHANDLER_OPER_AY_16, str);
871 sprintf(str, "OPER_%s_32()", g_ea_info_table[ea_mode].ea_add);
872 add_replace_string(replace, ID_OPHANDLER_OPER_AY_32, str);
875 /* Now write the function body with the selected replace strings */
876 write_body(filep, body, replace);
881 /* Generate opcode variants based on available addressing modes */
882 void generate_opcode_ea_variants(FILE* filep, body_struct* body, replace_struct* replace, opcode_struct* op)
884 int old_length = replace->length;
886 /* No ea modes available for this opcode */
887 if(HAS_NO_EA_MODE(op->ea_allowed))
889 generate_opcode_handler(filep, body, replace, op, EA_MODE_NONE);
893 /* Check for and create specific opcodes for each available addressing mode */
894 if(HAS_EA_AI(op->ea_allowed))
895 generate_opcode_handler(filep, body, replace, op, EA_MODE_AI);
896 replace->length = old_length;
897 if(HAS_EA_PI(op->ea_allowed))
899 generate_opcode_handler(filep, body, replace, op, EA_MODE_PI);
900 replace->length = old_length;
902 generate_opcode_handler(filep, body, replace, op, EA_MODE_PI7);
904 replace->length = old_length;
905 if(HAS_EA_PD(op->ea_allowed))
907 generate_opcode_handler(filep, body, replace, op, EA_MODE_PD);
908 replace->length = old_length;
910 generate_opcode_handler(filep, body, replace, op, EA_MODE_PD7);
912 replace->length = old_length;
913 if(HAS_EA_DI(op->ea_allowed))
914 generate_opcode_handler(filep, body, replace, op, EA_MODE_DI);
915 replace->length = old_length;
916 if(HAS_EA_IX(op->ea_allowed))
917 generate_opcode_handler(filep, body, replace, op, EA_MODE_IX);
918 replace->length = old_length;
919 if(HAS_EA_AW(op->ea_allowed))
920 generate_opcode_handler(filep, body, replace, op, EA_MODE_AW);
921 replace->length = old_length;
922 if(HAS_EA_AL(op->ea_allowed))
923 generate_opcode_handler(filep, body, replace, op, EA_MODE_AL);
924 replace->length = old_length;
925 if(HAS_EA_PCDI(op->ea_allowed))
926 generate_opcode_handler(filep, body, replace, op, EA_MODE_PCDI);
927 replace->length = old_length;
928 if(HAS_EA_PCIX(op->ea_allowed))
929 generate_opcode_handler(filep, body, replace, op, EA_MODE_PCIX);
930 replace->length = old_length;
931 if(HAS_EA_I(op->ea_allowed))
932 generate_opcode_handler(filep, body, replace, op, EA_MODE_I);
933 replace->length = old_length;
936 /* Generate variants of condition code opcodes */
937 void generate_opcode_cc_variants(FILE* filep, body_struct* body, replace_struct* replace, opcode_struct* op_in, int offset)
942 int old_length = replace->length;
943 opcode_struct* op = malloc(sizeof(opcode_struct));
947 op->op_mask |= 0x0f00;
949 /* Do all condition codes except t and f */
952 /* Add replace strings for this condition code */
953 sprintf(repl, "COND_%s()", g_cc_table[i][1]);
954 sprintf(replnot, "COND_NOT_%s()", g_cc_table[i][1]);
956 add_replace_string(replace, ID_OPHANDLER_CC, repl);
957 add_replace_string(replace, ID_OPHANDLER_NOT_CC, replnot);
959 /* Set the new opcode info */
960 strcpy(op->name+offset, g_cc_table[i][0]);
962 op->op_match = (op->op_match & 0xf0ff) | (i<<8);
964 /* Generate all opcode variants for this modified opcode */
965 generate_opcode_ea_variants(filep, body, replace, op);
966 /* Remove the above replace strings */
967 replace->length = old_length;
972 /* Process the opcode handlers section of the input file */
973 void process_opcode_handlers(void)
975 FILE* input_file = g_input_file;
977 char func_name[MAX_LINE_LENGTH+1];
978 char oper_name[MAX_LINE_LENGTH+1];
980 char oper_spec_proc[MAX_LINE_LENGTH+1];
981 char oper_spec_ea[MAX_LINE_LENGTH+1];
982 opcode_struct* opinfo;
983 replace_struct* replace = malloc(sizeof(replace_struct));
984 body_struct* body = malloc(sizeof(body_struct));
987 output_file = g_ops_ac_file;
991 /* Find the first line of the function */
993 while(strstr(func_name, ID_OPHANDLER_NAME) == NULL)
995 if(strcmp(func_name, ID_INPUT_SEPARATOR) == 0)
999 return; /* all done */
1001 if(fgetline(func_name, MAX_LINE_LENGTH, input_file) < 0)
1002 error_exit("Premature end of file when getting function name");
1004 /* Get the rest of the function */
1005 for(body->length=0;;body->length++)
1007 if(body->length > MAX_BODY_LENGTH)
1008 error_exit("Function too long");
1010 if(fgetline(body->body[body->length], MAX_LINE_LENGTH, input_file) < 0)
1011 error_exit("Premature end of file when getting function body");
1013 if(body->body[body->length][0] == '}')
1022 /* Extract the function name information */
1023 if(!extract_opcode_info(func_name, oper_name, &oper_size, oper_spec_proc, oper_spec_ea))
1024 error_exit("Invalid " ID_OPHANDLER_NAME " format");
1026 /* Find the corresponding table entry */
1027 opinfo = find_opcode(oper_name, oper_size, oper_spec_proc, oper_spec_ea);
1029 error_exit("Unable to find matching table entry for %s", func_name);
1031 /* Change output files if we pass 'c' or 'n' */
1032 if(output_file == g_ops_ac_file && oper_name[0] > 'c')
1033 output_file = g_ops_dm_file;
1034 else if(output_file == g_ops_dm_file && oper_name[0] > 'm')
1035 output_file = g_ops_nz_file;
1037 replace->length = 0;
1039 /* Generate opcode variants */
1040 if(strcmp(opinfo->name, "bcc") == 0 || strcmp(opinfo->name, "scc") == 0)
1041 generate_opcode_cc_variants(output_file, body, replace, opinfo, 1);
1042 else if(strcmp(opinfo->name, "dbcc") == 0)
1043 generate_opcode_cc_variants(output_file, body, replace, opinfo, 2);
1044 else if(strcmp(opinfo->name, "trapcc") == 0)
1045 generate_opcode_cc_variants(output_file, body, replace, opinfo, 4);
1047 generate_opcode_ea_variants(output_file, body, replace, opinfo);
1052 /* Populate the opcode handler table from the input file */
1053 void populate_table(void)
1056 char bitpattern[17];
1058 char buff[MAX_LINE_LENGTH];
1064 /* Find the start of the table */
1065 while(strcmp(buff, ID_TABLE_START) != 0)
1066 if(fgetline(buff, MAX_LINE_LENGTH, g_input_file) < 0)
1067 error_exit("Premature EOF while reading table");
1069 /* Process the entire table */
1070 for(op = g_opcode_input_table;;op++)
1072 if(fgetline(buff, MAX_LINE_LENGTH, g_input_file) < 0)
1073 error_exit("Premature EOF while reading table");
1074 if(strlen(buff) == 0)
1076 /* We finish when we find an input separator */
1077 if(strcmp(buff, ID_INPUT_SEPARATOR) == 0)
1080 /* Extract the info from the table */
1084 ptr += skip_spaces(ptr);
1085 ptr += check_strsncpy(op->name, ptr, MAX_NAME_LENGTH);
1088 ptr += skip_spaces(ptr);
1089 ptr += check_atoi(ptr, &temp);
1090 op->size = (unsigned char)temp;
1092 /* Special processing */
1093 ptr += skip_spaces(ptr);
1094 ptr += check_strsncpy(op->spec_proc, ptr, MAX_SPEC_PROC_LENGTH);
1096 /* Specified EA Mode */
1097 ptr += skip_spaces(ptr);
1098 ptr += check_strsncpy(op->spec_ea, ptr, MAX_SPEC_EA_LENGTH);
1100 /* Bit Pattern (more processing later) */
1101 ptr += skip_spaces(ptr);
1102 ptr += check_strsncpy(bitpattern, ptr, 17);
1104 /* Allowed Addressing Mode List */
1105 ptr += skip_spaces(ptr);
1106 ptr += check_strsncpy(op->ea_allowed, ptr, EA_ALLOWED_LENGTH);
1108 /* CPU operating mode (U = user or supervisor, S = supervisor only */
1109 ptr += skip_spaces(ptr);
1110 for(i=0;i<NUM_CPUS;i++)
1112 op->cpu_mode[i] = *ptr++;
1113 ptr += skip_spaces(ptr);
1116 /* Allowed CPUs for this instruction */
1117 for(i=0;i<NUM_CPUS;i++)
1119 ptr += skip_spaces(ptr);
1120 if(*ptr == UNSPECIFIED_CH)
1122 op->cpus[i] = UNSPECIFIED_CH;
1128 op->cpus[i] = (char)('0' + i);
1129 ptr += check_atoi(ptr, &temp);
1130 op->cycles[i] = (unsigned char)temp;
1134 /* generate mask and match from bitpattern */
1139 op->op_mask |= (bitpattern[i] != '.') << (15-i);
1140 op->op_match |= (bitpattern[i] == '1') << (15-i);
1143 /* Terminate the list */
1147 /* Read a header or footer insert from the input file */
1148 void read_insert(char* insert)
1151 char* overflow = insert + MAX_INSERT_LENGTH - MAX_LINE_LENGTH;
1153 char* first_blank = NULL;
1155 /* Skip any leading blank lines */
1156 for(length = 0;length == 0;length = fgetline(ptr, MAX_LINE_LENGTH, g_input_file))
1158 error_exit("Buffer overflow reading inserts");
1160 error_exit("Premature EOF while reading inserts");
1162 /* Advance and append newline */
1164 strcpy(ptr++, "\n");
1166 /* Read until next separator */
1169 /* Read a new line */
1171 error_exit("Buffer overflow reading inserts");
1172 if((length = fgetline(ptr, MAX_LINE_LENGTH, g_input_file)) < 0)
1173 error_exit("Premature EOF while reading inserts");
1175 /* Stop if we read a separator */
1176 if(strcmp(ptr, ID_INPUT_SEPARATOR) == 0)
1179 /* keep track in case there are trailing blanks */
1182 if(first_blank == NULL)
1188 /* Advance and append newline */
1190 strcpy(ptr++, "\n");
1193 /* kill any trailing blank lines */
1201 /* ======================================================================== */
1202 /* ============================= MAIN FUNCTION ============================ */
1203 /* ======================================================================== */
1205 int main(int argc, char **argv)
1208 char output_path[MAX_DIR] = "";
1209 char filename[MAX_PATH];
1210 /* Section identifier */
1211 char section_id[MAX_LINE_LENGTH+1];
1213 char temp_insert[MAX_INSERT_LENGTH+1];
1214 char prototype_footer_insert[MAX_INSERT_LENGTH+1];
1215 char table_footer_insert[MAX_INSERT_LENGTH+1];
1216 char ophandler_footer_insert[MAX_INSERT_LENGTH+1];
1217 /* Flags if we've processed certain parts already */
1218 int prototype_header_read = 0;
1219 int prototype_footer_read = 0;
1220 int table_header_read = 0;
1221 int table_footer_read = 0;
1222 int ophandler_header_read = 0;
1223 int ophandler_footer_read = 0;
1224 int table_body_read = 0;
1225 int ophandler_body_read = 0;
1227 printf("\n\t\tMusashi v%s 68000, 68010, 68EC020, 68020 emulator\n", g_version);
1228 printf("\t\tCopyright 1998-2000 Karl Stenerud (karl@mame.net)\n\n");
1230 /* Check if output path and source for the input file are given */
1234 strcpy(output_path, argv[1]);
1236 for(ptr = strchr(output_path, '\\'); ptr; ptr = strchr(ptr, '\\'))
1238 if(output_path[strlen(output_path)-1] != '/')
1239 strcat(output_path, "/");
1241 strcpy(g_input_filename, argv[2]);
1245 /* Open the files we need */
1246 sprintf(filename, "%s%s", output_path, FILENAME_PROTOTYPE);
1247 if((g_prototype_file = fopen(filename, "wt")) == NULL)
1248 perror_exit("Unable to create prototype file (%s)\n", filename);
1250 sprintf(filename, "%s%s", output_path, FILENAME_TABLE);
1251 if((g_table_file = fopen(filename, "wt")) == NULL)
1252 perror_exit("Unable to create table file (%s)\n", filename);
1254 sprintf(filename, "%s%s", output_path, FILENAME_OPS_AC);
1255 if((g_ops_ac_file = fopen(filename, "wt")) == NULL)
1256 perror_exit("Unable to create ops ac file (%s)\n", filename);
1258 sprintf(filename, "%s%s", output_path, FILENAME_OPS_DM);
1259 if((g_ops_dm_file = fopen(filename, "wt")) == NULL)
1260 perror_exit("Unable to create ops dm file (%s)\n", filename);
1262 sprintf(filename, "%s%s", output_path, FILENAME_OPS_NZ);
1263 if((g_ops_nz_file = fopen(filename, "wt")) == NULL)
1264 perror_exit("Unable to create ops nz file (%s)\n", filename);
1266 if((g_input_file=fopen(g_input_filename, "rt")) == NULL)
1267 perror_exit("can't open %s for input", g_input_filename);
1270 /* Get to the first section of the input file */
1272 while(strcmp(section_id, ID_INPUT_SEPARATOR) != 0)
1273 if(fgetline(section_id, MAX_LINE_LENGTH, g_input_file) < 0)
1274 error_exit("Premature EOF while reading input file");
1276 /* Now process all sections */
1279 if(fgetline(section_id, MAX_LINE_LENGTH, g_input_file) < 0)
1280 error_exit("Premature EOF while reading input file");
1281 if(strcmp(section_id, ID_PROTOTYPE_HEADER) == 0)
1283 if(prototype_header_read)
1284 error_exit("Duplicate prototype header");
1285 read_insert(temp_insert);
1286 fprintf(g_prototype_file, "%s\n\n", temp_insert);
1287 prototype_header_read = 1;
1289 else if(strcmp(section_id, ID_TABLE_HEADER) == 0)
1291 if(table_header_read)
1292 error_exit("Duplicate table header");
1293 read_insert(temp_insert);
1294 fprintf(g_table_file, "%s", temp_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(temp_insert);
1302 fprintf(g_ops_ac_file, "%s\n\n", temp_insert);
1303 fprintf(g_ops_dm_file, "%s\n\n", temp_insert);
1304 fprintf(g_ops_nz_file, "%s\n\n", temp_insert);
1305 ophandler_header_read = 1;
1307 else if(strcmp(section_id, ID_PROTOTYPE_FOOTER) == 0)
1309 if(prototype_footer_read)
1310 error_exit("Duplicate prototype footer");
1311 read_insert(prototype_footer_insert);
1312 prototype_footer_read = 1;
1314 else if(strcmp(section_id, ID_TABLE_FOOTER) == 0)
1316 if(table_footer_read)
1317 error_exit("Duplicate table footer");
1318 read_insert(table_footer_insert);
1319 table_footer_read = 1;
1321 else if(strcmp(section_id, ID_OPHANDLER_FOOTER) == 0)
1323 if(ophandler_footer_read)
1324 error_exit("Duplicate opcode handler footer");
1325 read_insert(ophandler_footer_insert);
1326 ophandler_footer_read = 1;
1328 else if(strcmp(section_id, ID_TABLE_BODY) == 0)
1330 if(!prototype_header_read)
1331 error_exit("Table body encountered before prototype header");
1332 if(!table_header_read)
1333 error_exit("Table body encountered before table header");
1334 if(!ophandler_header_read)
1335 error_exit("Table body encountered before opcode handler header");
1338 error_exit("Duplicate table body");
1341 table_body_read = 1;
1343 else if(strcmp(section_id, ID_OPHANDLER_BODY) == 0)
1345 if(!prototype_header_read)
1346 error_exit("Opcode handlers encountered before prototype header");
1347 if(!table_header_read)
1348 error_exit("Opcode handlers encountered before table header");
1349 if(!ophandler_header_read)
1350 error_exit("Opcode handlers encountered before opcode handler header");
1351 if(!table_body_read)
1352 error_exit("Opcode handlers encountered before table body");
1354 if(ophandler_body_read)
1355 error_exit("Duplicate opcode handler section");
1357 process_opcode_handlers();
1359 ophandler_body_read = 1;
1361 else if(strcmp(section_id, ID_END) == 0)
1363 /* End of input file. Do a sanity check and then write footers */
1364 if(!prototype_header_read)
1365 error_exit("Missing prototype header");
1366 if(!prototype_footer_read)
1367 error_exit("Missing prototype footer");
1368 if(!table_header_read)
1369 error_exit("Missing table header");
1370 if(!table_footer_read)
1371 error_exit("Missing table footer");
1372 if(!table_body_read)
1373 error_exit("Missing table body");
1374 if(!ophandler_header_read)
1375 error_exit("Missing opcode handler header");
1376 if(!ophandler_footer_read)
1377 error_exit("Missing opcode handler footer");
1378 if(!ophandler_body_read)
1379 error_exit("Missing opcode handler body");
1381 print_opcode_output_table(g_table_file);
1383 fprintf(g_prototype_file, "%s\n\n", prototype_footer_insert);
1384 fprintf(g_table_file, "%s\n\n", table_footer_insert);
1385 fprintf(g_ops_ac_file, "%s\n\n", ophandler_footer_insert);
1386 fprintf(g_ops_dm_file, "%s\n\n", ophandler_footer_insert);
1387 fprintf(g_ops_nz_file, "%s\n\n", ophandler_footer_insert);
1393 error_exit("Unknown section identifier: %s", section_id);
1397 /* Close all files and exit */
1398 fclose(g_prototype_file);
1399 fclose(g_table_file);
1400 fclose(g_ops_ac_file);
1401 fclose(g_ops_dm_file);
1402 fclose(g_ops_nz_file);
1403 fclose(g_input_file);
1405 printf("Generated %d opcode handlers from %d primitives\n", g_num_functions, g_num_primitives);
1412 /* ======================================================================== */
1413 /* ============================== END OF FILE ============================= */
1414 /* ======================================================================== */