1 /* This file is part of the program psim.
3 Copyright (C) 1994-1995, Andrew Cagney <cagney@highland.com.au>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 #include "ppc-config.h"
36 #ifndef STATIC_INLINE_PSIM
37 #define STATIC_INLINE_PSIM STATIC_INLINE
42 #include "cpu.h" /* includes psim.h */
58 /* Any starting address less than this is assumed to be an OEA program
60 #ifndef OEA_START_ADDRESS
61 #define OEA_START_ADDRESS 4096
64 /* Any starting address greater than this is assumed to be an OpenBoot
66 #ifndef OPENBOOT_START_ADDRESS
67 #define OPENBOOT_START_ADDRESS 0x80000000
70 #ifndef OEA_MEMORY_SIZE
71 #define OEA_MEMORY_SIZE 0x100000
75 /* system structure, actual size of processor array determined at
83 /* escape routine for inner functions */
85 void *path_to_restart;
86 /* status from last halt */
87 psim_status halt_status;
88 /* the processes proper */
90 int last_cpu; /* CPU that last (tried to) execute an instruction */
91 cpu *processors[MAX_NR_PROCESSORS];
95 int current_target_byte_order;
96 int current_host_byte_order;
97 int current_environment;
98 int current_alignment;
99 int current_floating_point;
102 /* create a device tree from the image */
106 /* Raw hardware tree:
108 A small default set of devices are configured. Each section of the
109 image is loaded directly into physical memory. */
111 STATIC_INLINE_PSIM void
112 create_hardware_device_tree(bfd *image,
116 const memory_size = OEA_MEMORY_SIZE;
119 device_tree_add_passthrough(root, "/options");
120 device_tree_add_integer(root, "/options/smp",
122 device_tree_add_boolean(root, "/options/little-endian?",
123 !image->xvec->byteorder_big_p);
124 device_tree_add_string(root, "/options/env",
126 device_tree_add_boolean(root, "/options/strict-alignment?",
127 (WITH_ALIGNMENT == STRICT_ALIGNMENT
128 || !image->xvec->byteorder_big_p));
129 device_tree_add_boolean(root, "/options/floating-point?",
130 WITH_FLOATING_POINT);
133 name = printd_uw_u_u("/memory", 0, memory_size, access_read_write_exec);
134 device_tree_add_found_device(root, name);
136 device_tree_add_found_device(root, "/iobus@0x400000");
137 device_tree_add_found_device(root, "/iobus/console@0x000000,16");
138 device_tree_add_found_device(root, "/iobus/halt@0x100000,4");
139 device_tree_add_found_device(root, "/iobus/icu@0x200000,4");
142 device_tree_add_passthrough(root, "/init");
143 device_tree_add_found_device(root, "/init/register@pc,0x0");
144 name = printd_c_uw("/init/register", "sp", memory_size);
145 device_tree_add_found_device(root, name);
147 name = printd_c_uw("/init/register", "msr",
148 (image->xvec->byteorder_big_p
150 : msr_little_endian_mode));
151 device_tree_add_found_device(root, name);
153 /* AJC puts the PC at zero and wants a stack while MM puts it above
154 zero and doesn't. Really there should be no stack *but* this
155 makes testing easier */
156 device_tree_add_found_device(root,
157 (bfd_get_start_address(image) == 0
159 : "/init/stack@none"));
160 name = printd_c("/init/load-binary", bfd_get_filename(image));
161 device_tree_add_found_device(root, name);
166 /* Openboot model (under development):
168 An extension of the hardware model. The image is read into memory
169 as a single block. Sections of the image are then mapped as
170 required using a HTAB. */
172 STATIC_INLINE_PSIM void
173 create_openboot_device_tree(bfd *image,
176 create_hardware_device_tree(image, root);
182 Image sections loaded into virtual addresses as specified. A
183 (large) stack is reserved (but only allocated as needed). System
184 calls that include suport for heap growth are attached. */
186 STATIC_INLINE_PSIM void
187 create_vea_device_tree(bfd *image,
190 unsigned_word top_of_stack;
195 /* establish a few defaults */
196 if (image->xvec->flavour == bfd_target_elf_flavour) {
198 top_of_stack = 0xe0000000;
199 stack_size = 0x00100000;
203 top_of_stack = 0x20000000;
204 stack_size = 0x00100000;
208 device_tree_add_passthrough(root, "/options");
209 device_tree_add_integer(root, "/options/smp", 1); /* always */
210 device_tree_add_boolean(root, "/options/little-endian?",
211 !image->xvec->byteorder_big_p);
212 device_tree_add_string(root, "/options/env",
213 (WITH_ENVIRONMENT == USER_ENVIRONMENT
214 ? "user" : "virtual"));
215 device_tree_add_boolean(root, "/options/strict-alignment?",
216 (WITH_ALIGNMENT == STRICT_ALIGNMENT
217 || !image->xvec->byteorder_big_p));
218 device_tree_add_boolean(root, "/options/floating-point?",
219 WITH_FLOATING_POINT);
221 /* virtual memory - handles growth of stack/heap */
222 name = printd_uw_u("/vm", top_of_stack - stack_size, stack_size);
223 device_tree_add_found_device(root, name);
225 name = printd_c("/vm/map-binary", bfd_get_filename(image));
226 device_tree_add_found_device(root, name);
229 /* finish the init */
230 device_tree_add_passthrough(root, "/init");
231 name = printd_c_uw("/init/register", "pc", bfd_get_start_address(image));
232 device_tree_add_found_device(root, name); /*pc*/
234 name = printd_c_uw("/init/register", "sp", top_of_stack);
235 device_tree_add_found_device(root, name);
237 name = printd_c_uw("/init/register", "msr",
238 (image->xvec->byteorder_big_p
240 : msr_little_endian_mode));
241 device_tree_add_found_device(root, name);
243 device_tree_add_found_device(root, (elf_binary
245 : "/init/stack@xcoff"));
251 The file contains lines that specify the describe the device tree
252 to be created, read them in and load them into the tree */
254 STATIC_INLINE_PSIM void
255 create_filed_device_tree(const char *file_name,
258 FILE *description = fopen(file_name, "r");
260 char device_path[1000];
261 while (fgets(device_path, sizeof(device_path), description)) {
262 /* check all of line was read */
264 char *end = strchr(device_path, '\n');
267 error("create_filed_device_tree() line %d to long: %s\n",
268 line_nr, device_path);
273 /* check for leading comment */
274 if (device_path[0] != '/')
276 /* enter it in varying ways */
277 if (strchr(device_path, '@') != NULL) {
278 device_tree_add_found_device(root, device_path);
281 char *space = strchr(device_path, ' ');
283 /* intermediate node */
284 device_tree_add_passthrough(root, device_path);
286 else if (space[-1] == '?') {
289 device_tree_add_boolean(root, device_path, space[1] != '0');
291 else if (isdigit(space[1])) {
294 device_tree_add_integer(root, device_path, strtoul(space+1, 0, 0));
296 else if (space[1] == '"') {
298 char *end = strchr(space+2, '\0');
302 device_tree_add_string(root, device_path, space + 2);
307 device_tree_add_string(root, device_path, space + 1);
315 /* Given the file containing the `image', create a device tree that
316 defines the machine to be modeled */
318 STATIC_INLINE_PSIM device_tree *
319 create_device_tree(const char *file_name,
323 const device *core_device = core_device_create(memory);
324 device_tree *root = device_tree_add_device(NULL, "/", core_device);
326 bfd_init(); /* could be redundant but ... */
329 image = bfd_openr(file_name, NULL);
331 bfd_perror("open failed:");
332 error("nothing loaded\n");
335 /* check it is valid */
336 if (!bfd_check_format(image, bfd_object)) {
337 printf_filtered("create_device_tree() - FIXME - should check more bfd bits\n");
338 printf_filtered("create_device_tree() - %s not an executable, assume device file\n", file_name);
343 /* depending on what was found about the file, load it */
345 if (bfd_get_start_address(image) < OEA_START_ADDRESS) {
346 TRACE(trace_device_tree, ("create_device_tree() - hardware image\n"));
347 create_hardware_device_tree(image, root);
349 else if (bfd_get_start_address(image) < OPENBOOT_START_ADDRESS) {
350 TRACE(trace_device_tree, ("create_device_tree() - vea image\n"));
351 create_vea_device_tree(image, root);
354 TRACE(trace_device_tree, ("create_device_tree() - openboot? image\n"));
355 create_openboot_device_tree(image, root);
360 TRACE(trace_device_tree, ("create_device_tree() - text image\n"));
361 create_filed_device_tree(file_name, root);
370 psim_create(const char *file_name)
377 system = ZALLOC(psim);
378 system->events = event_queue_create();
379 system->memory = core_create();
380 system->monitor = mon_create();
381 system->devices = create_device_tree(file_name, system->memory);
382 for (cpu_nr = 0; cpu_nr < MAX_NR_PROCESSORS; cpu_nr++) {
383 system->processors[cpu_nr] = cpu_create(system,
386 mon_cpu(system->monitor,
391 /* fill in the missing real number of CPU's */
392 system->nr_cpus = device_tree_find_integer(system->devices,
395 /* fill in the missing TARGET BYTE ORDER information */
396 current_target_byte_order = (device_tree_find_boolean(system->devices,
397 "/options/little-endian?")
400 if (CURRENT_TARGET_BYTE_ORDER != current_target_byte_order)
401 error("target byte order conflict\n");
403 /* fill in the missing HOST BYTE ORDER information */
404 current_host_byte_order = (current_host_byte_order = 1,
405 (*(char*)(¤t_host_byte_order)
408 if (CURRENT_HOST_BYTE_ORDER != current_host_byte_order)
409 error("host byte order conflict\n");
411 /* fill in the missing OEA/VEA information */
412 env = device_tree_find_string(system->devices,
414 current_environment = ((strcmp(env, "user") == 0
415 || strcmp(env, "uea") == 0)
417 : (strcmp(env, "virtual") == 0
418 || strcmp(env, "vea") == 0)
419 ? VIRTUAL_ENVIRONMENT
420 : (strcmp(env, "operating") == 0
421 || strcmp(env, "oea") == 0)
422 ? OPERATING_ENVIRONMENT
424 if (current_environment == 0)
425 error("unreconized /options/env\n");
426 if (CURRENT_ENVIRONMENT != current_environment)
427 error("target environment conflict\n");
429 /* fill in the missing ALLIGNMENT information */
430 current_alignment = (device_tree_find_boolean(system->devices,
431 "/options/strict-alignment?")
433 : NONSTRICT_ALIGNMENT);
434 if (CURRENT_ALIGNMENT != current_alignment)
435 error("target alignment conflict\n");
437 /* fill in the missing FLOATING POINT information */
438 current_floating_point = (device_tree_find_boolean(system->devices,
439 "/options/floating-point?")
440 ? HARD_FLOATING_POINT
441 : SOFT_FLOATING_POINT);
442 if (CURRENT_FLOATING_POINT != current_floating_point)
443 error("target floating-point conflict\n");
449 /* allow the simulation to stop/restart abnormaly */
451 STATIC_INLINE_PSIM void
452 psim_set_halt_and_restart(psim *system,
454 void *restart_jmp_buf)
456 system->path_to_halt = halt_jmp_buf;
457 system->path_to_restart = restart_jmp_buf;
460 STATIC_INLINE_PSIM void
461 psim_clear_halt_and_restart(psim *system)
463 system->path_to_halt = NULL;
464 system->path_to_restart = NULL;
468 psim_restart(psim *system,
471 system->last_cpu = current_cpu;
472 longjmp(*(jmp_buf*)(system->path_to_restart), current_cpu + 1);
477 psim_halt(psim *system,
483 system->last_cpu = current_cpu;
484 system->halt_status.cpu_nr = current_cpu;
485 system->halt_status.reason = reason;
486 system->halt_status.signal = signal;
487 system->halt_status.program_counter = cia;
488 longjmp(*(jmp_buf*)(system->path_to_halt), current_cpu + 1);
491 INLINE_PSIM psim_status
492 psim_get_status(psim *system)
494 return system->halt_status;
499 psim_cpu(psim *system,
502 if (cpu_nr < 0 || cpu_nr >= system->nr_cpus)
505 return system->processors[cpu_nr];
510 psim_device(psim *system,
513 return device_tree_find_device(system->devices, path);
519 psim_init(psim *system)
523 /* scrub the monitor */
524 mon_init(system->monitor, system->nr_cpus);
526 /* scrub all the cpus */
527 for (cpu_nr = 0; cpu_nr < system->nr_cpus; cpu_nr++)
528 cpu_init(system->processors[cpu_nr]);
530 /* init all the devices */
531 device_tree_init(system->devices, system);
533 /* force loop to restart */
534 system->last_cpu = system->nr_cpus - 1;
538 psim_stack(psim *system,
542 /* pass the stack device the argv/envp and let it work out what to
544 const device *stack_device = device_tree_find_device(system->devices,
546 unsigned_word stack_pointer;
547 psim_read_register(system, 0, &stack_pointer, "sp", cooked_transfer);
548 stack_device->callback->ioctl(stack_device,
559 /* EXECUTE REAL CODE:
561 Unfortunatly, there are multiple cases to consider vis:
563 <icache> X <smp> X <events> X <keep-running-flag> X ...
565 Consequently this function is written in multiple different ways */
567 STATIC_INLINE_PSIM void
568 run_until_stop(psim *system,
569 volatile int *keep_running)
574 #if WITH_IDECODE_CACHE_SIZE
575 for (cpu_nr = 0; cpu_nr < system->nr_cpus; cpu_nr++)
576 cpu_flush_icache(system->processors[cpu_nr]);
578 psim_set_halt_and_restart(system, &halt, &restart);
580 #if (!WITH_IDECODE_CACHE_SIZE && WITH_SMP == 0)
582 /* CASE 1: No instruction cache and no SMP.
584 In this case, we can take advantage of the fact that the current
585 instruction address does not need to be returned to the cpu
586 object after every execution of an instruction. Instead it only
587 needs to be saved when either A. the main loop exits or B. a
588 cpu-{halt,restart} call forces the loop to be re-entered. The
589 later functions always save the current cpu instruction
594 if (!setjmp(restart)) {
595 cpu *const processor = system->processors[0];
596 unsigned_word cia = cpu_get_program_counter(processor);
599 if (event_queue_tick(system->events)) {
600 cpu_set_program_counter(processor, cia);
601 event_queue_process(system->events);
602 cia = cpu_get_program_counter(processor);
606 instruction_word const instruction
607 = vm_instruction_map_read(cpu_instruction_map(processor),
609 cia = idecode_issue(processor, instruction, cia);
611 } while (keep_running == NULL || *keep_running);
612 cpu_set_program_counter(processor, cia);
614 } while(keep_running == NULL || *keep_running);
619 #if (WITH_IDECODE_CACHE_SIZE && WITH_SMP == 0)
621 /* CASE 2: Instruction case but no SMP
623 Here, the additional complexity comes from there being two
624 different cache implementations. A simple function address cache
625 or a full cracked instruction cache */
629 if (!setjmp(restart)) {
630 cpu *const processor = system->processors[0];
631 unsigned_word cia = cpu_get_program_counter(processor);
634 if (event_queue_tick(system->events)) {
635 cpu_set_program_counter(processor, cia);
636 event_queue_process(system->events);
637 cia = cpu_get_program_counter(processor);
640 idecode_cache *const cache_entry = cpu_icache_entry(processor,
642 if (cache_entry->address == cia) {
643 idecode_semantic *const semantic = cache_entry->semantic;
644 cia = semantic(processor, cache_entry, cia);
647 instruction_word const instruction
648 = vm_instruction_map_read(cpu_instruction_map(processor),
651 idecode_semantic *const semantic = idecode(processor,
655 cache_entry->address = cia;
656 cache_entry->semantic = semantic;
657 cia = semantic(processor, cache_entry, cia);
660 } while (keep_running == NULL || *keep_running);
661 cpu_set_program_counter(processor, cia);
663 } while(keep_running == NULL || *keep_running);
668 #if (!WITH_IDECODE_CACHE_SIZE && WITH_SMP > 0)
670 /* CASE 3: No ICACHE but SMP
672 The complexity here comes from needing to correctly restart the
673 system when it is aborted. In particular if cpu0 requests a
674 restart, the next cpu is still cpu1. Cpu0 being restarted after
675 all the other CPU's and the event queue have been processed */
678 int first_cpu = setjmp(restart);
680 first_cpu = system->last_cpu + 1;
683 for (current_cpu = first_cpu, first_cpu = 0;
684 current_cpu < system->nr_cpus + (WITH_EVENTS ? 1 : 0);
686 if (WITH_EVENTS && current_cpu == system->nr_cpus) {
687 if (event_queue_tick(system->events))
688 event_queue_process(system->events);
691 cpu *const processor = system->processors[current_cpu];
692 unsigned_word const cia = cpu_get_program_counter(processor);
693 instruction_word instruction =
694 vm_instruction_map_read(cpu_instruction_map(processor),
697 cpu_set_program_counter(processor,
698 idecode_issue(processor, instruction, cia));
700 if (!(keep_running == NULL || *keep_running)) {
701 system->last_cpu = current_cpu;
705 } while (keep_running == NULL || *keep_running);
709 #if (WITH_IDECODE_CACHE_SIZE && WITH_SMP > 0)
711 /* CASE 4: ICACHE and SMP ...
713 This time, everything goes wrong. Need to restart loops
714 correctly, need to save the program counter and finally need to
715 keep track of each processors current address! */
718 int first_cpu = setjmp(restart);
720 first_cpu = system->last_cpu + 1;
723 for (current_cpu = first_cpu, first_cpu = 0;
724 current_cpu < system->nr_cpus + (WITH_EVENTS ? 1 : 0);
726 if (WITH_EVENTS && current_cpu == system->nr_cpus) {
727 if (event_queue_tick(system->events))
728 event_queue_process(system->events);
731 cpu *processor = system->processors[current_cpu];
732 unsigned_word const cia = cpu_get_program_counter(processor);
733 idecode_cache *cache_entry = cpu_icache_entry(processor, cia);
734 if (cache_entry->address == cia) {
735 idecode_semantic *semantic = cache_entry->semantic;
736 cpu_set_program_counter(processor,
737 semantic(processor, cache_entry, cia));
740 instruction_word instruction =
741 vm_instruction_map_read(cpu_instruction_map(processor),
744 idecode_semantic *semantic = idecode(processor,
748 cache_entry->address = cia;
749 cache_entry->semantic = semantic;
750 cpu_set_program_counter(processor,
751 semantic(processor, cache_entry, cia));
754 if (!(keep_running == NULL || *keep_running))
757 } while (keep_running == NULL || *keep_running);
761 psim_clear_halt_and_restart(system);
765 /* SIMULATE INSTRUCTIONS, various different ways of achieving the same
769 psim_step(psim *system)
771 volatile int keep_running = 0;
772 run_until_stop(system, &keep_running);
776 psim_run(psim *system)
778 run_until_stop(system, NULL);
782 psim_run_until_stop(psim *system,
783 volatile int *keep_running)
785 run_until_stop(system, keep_running);
790 /* storage manipulation functions */
793 psim_read_register(psim *system,
799 register_descriptions description;
800 char cooked_buf[sizeof(natural_word)];
803 /* find our processor */
804 if (which_cpu == MAX_NR_PROCESSORS)
805 which_cpu = system->last_cpu;
806 if (which_cpu < 0 || which_cpu >= system->nr_cpus)
807 error("psim_read_register() - invalid processor %d\n", which_cpu);
808 processor = system->processors[which_cpu];
810 /* find the register description */
811 description = register_description(reg);
812 if (description.type == reg_invalid)
813 error("psim_read_register() invalid register name `%s'\n", reg);
815 /* get the cooked value */
816 switch (description.type) {
819 *(gpreg*)cooked_buf = cpu_registers(processor)->gpr[description.index];
823 *(spreg*)cooked_buf = cpu_registers(processor)->spr[description.index];
827 *(sreg*)cooked_buf = cpu_registers(processor)->sr[description.index];
831 *(fpreg*)cooked_buf = cpu_registers(processor)->fpr[description.index];
835 *(unsigned_word*)cooked_buf = cpu_get_program_counter(processor);
839 *(creg*)cooked_buf = cpu_registers(processor)->cr;
843 *(msreg*)cooked_buf = cpu_registers(processor)->msr;
847 printf_filtered("psim_read_register(processor=0x%x,buf=0x%x,reg=%s) %s\n",
849 "read of this register unimplemented");
854 /* the PSIM internal values are in host order. To fetch raw data,
855 they need to be converted into target order and then returned */
856 if (mode == raw_transfer) {
857 /* FIXME - assumes that all registers are simple integers */
858 switch (description.size) {
860 *(unsigned_1*)buf = H2T_1(*(unsigned_1*)cooked_buf);
863 *(unsigned_2*)buf = H2T_2(*(unsigned_2*)cooked_buf);
866 *(unsigned_4*)buf = H2T_4(*(unsigned_4*)cooked_buf);
869 *(unsigned_8*)buf = H2T_8(*(unsigned_8*)cooked_buf);
874 bcopy(cooked_buf, buf, description.size);
882 psim_write_register(psim *system,
889 register_descriptions description;
890 char cooked_buf[sizeof(natural_word)];
892 /* find our processor */
893 if (which_cpu == MAX_NR_PROCESSORS)
894 which_cpu = system->last_cpu;
895 if (which_cpu == -1) {
897 for (i = 0; i < system->nr_cpus; i++)
898 psim_write_register(system, i, buf, reg, mode);
901 else if (which_cpu < 0 || which_cpu >= system->nr_cpus) {
902 error("psim_read_register() - invalid processor %d\n", which_cpu);
905 processor = system->processors[which_cpu];
907 /* find the description of the register */
908 description = register_description(reg);
909 if (description.type == reg_invalid)
910 error("psim_write_register() invalid register name %s\n", reg);
912 /* If the data is comming in raw (target order), need to cook it
913 into host order before putting it into PSIM's internal structures */
914 if (mode == raw_transfer) {
915 switch (description.size) {
917 *(unsigned_1*)cooked_buf = T2H_1(*(unsigned_1*)buf);
920 *(unsigned_2*)cooked_buf = T2H_2(*(unsigned_2*)buf);
923 *(unsigned_4*)cooked_buf = T2H_4(*(unsigned_4*)buf);
926 *(unsigned_8*)cooked_buf = T2H_8(*(unsigned_8*)buf);
931 bcopy(buf, cooked_buf, description.size);
934 /* put the cooked value into the register */
935 switch (description.type) {
938 cpu_registers(processor)->gpr[description.index] = *(gpreg*)cooked_buf;
942 cpu_registers(processor)->fpr[description.index] = *(fpreg*)cooked_buf;
946 cpu_set_program_counter(processor, *(unsigned_word*)cooked_buf);
950 cpu_registers(processor)->spr[description.index] = *(spreg*)cooked_buf;
954 cpu_registers(processor)->sr[description.index] = *(sreg*)cooked_buf;
958 cpu_registers(processor)->cr = *(creg*)cooked_buf;
962 cpu_registers(processor)->msr = *(msreg*)cooked_buf;
966 printf_filtered("psim_write_register(processor=0x%x,cooked_buf=0x%x,reg=%s) %s\n",
967 processor, cooked_buf, reg,
968 "read of this register unimplemented");
978 psim_read_memory(psim *system,
985 if (which_cpu == MAX_NR_PROCESSORS)
986 which_cpu = system->last_cpu;
987 if (which_cpu < 0 || which_cpu >= system->nr_cpus)
988 error("psim_read_memory() invalid cpu\n");
989 processor = system->processors[which_cpu];
990 return vm_data_map_read_buffer(cpu_data_map(processor),
991 buffer, vaddr, nr_bytes);
996 psim_write_memory(psim *system,
1001 int violate_read_only_section)
1004 if (which_cpu == MAX_NR_PROCESSORS)
1005 which_cpu = system->last_cpu;
1006 if (which_cpu < 0 || which_cpu >= system->nr_cpus)
1007 error("psim_read_memory() invalid cpu\n");
1008 processor = system->processors[which_cpu];
1009 return vm_data_map_write_buffer(cpu_data_map(processor),
1010 buffer, vaddr, nr_bytes, 1);
1015 psim_print_info(psim *system,
1018 mon_print_info(system->monitor, verbose);
1022 #endif /* _PSIM_C_ */