Join GitHub today
GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.
C Perl Shell
Fetching latest commit…
Cannot retrieve the latest commit at this time.
|Failed to load latest commit information.|
$Id: README,v 1.11 2002/10/01 22:32:27 rowan Exp $ This is exhaust version 1.9, a memory array redcode simulator. It is mostly in the public domain (see the file COPYING for details). Files: changelog Log of Change. COPYING lack of license. Makefile file for make. README This file. NEWS Notes about things that might break. asm.c (Dis)Assembler. asm.h Token codes and prototypes for assembler. exhaust.c Simplified pMARS-like simulator. exhaust.h Global constants, structures and types. insn.h Instruction encoding specs. sim.c Simulator proper. sim.h Simulator prototypes. pspace.c P-space implementation. pspace.h P-space prototypes. test.pl Perl program to test for differences between simulators. t/*.red Redcode for tests. t/*.rc Load files for tests. To compile on UNIX or on DOS with djgpp first edit the makefile to your comfort and then run `make' to get an executable `exhaust'. If you have gcc you hopefully don't need to make any large makefile modifications. See the section `How to use the simulator and friends' below for an overview of how to use the simulator in your programs. Thanks to the pMARS authors and Ken Espiritu for ideas implemented in this simulator. Especially Ken's effective addressing calculation code from pMARS 0.8.6 was adapted for a great speed-up. * How to use the simulator and friends -------------------------------------- This is a quick run-down on how to use the simulator and assembler with your program. Special considerations for P-Space are detailed in the section about p-space below, so here non-P warriors are assumed. After munging your source file, remember to link your executable with sim.o, pspace.o, and asm.o! You don't need asm.o if you don't use the assembler or disassembler. The main things that you should do are: - generate (perhaps by assembling) the warrior code - allocate the internal buffers - clear core and load the warriors into it - call the simulator to do it's thing In the examples, I assume you use a warrior structure similar to exhaust.h's struct warrior_st, but it is not canonical in any way. Here I rely on the following fields: insn_t code[ MAXLENGTH ]; /* code of warrior */ int len; /* length of warrior code */ int start; /* start relative to first insn */ * The assembler: i) You need some declarations and includes: #include "exhaust.h" #include "asm.h" warrior_t w1, w2; /* warrior structs hold code and misc. info */ int CORESIZE = 8000, /* size of core */ PROCESSES = 8000, /* max. number of processes per warrior */ NWARRIORS = 2, /* number of warriors */ CYCLES = 80000; /* cycles until tie */ ii) Then you want to assemble your warriors, say aeka and Rave: asm_fname( "aeka.rc", &w1, CORESIZE ); asm_fname( "rave.rc", &w2, CORESIZE ); You could also use asm_file(FILE *, warrior_t *, CORESIZE) to assemble from a stream. * Simulator: i) The simulator interface is simple. First you need to allocate and query the address of the core memory: #include "exhaust.h" #include "sim.h" insn_t *core; core = sim_alloc_bufs( NWARRIORS, CORESIZE, PROCESSES, CYCLES ); If any of the allocations failed the returned value will be NULL. You should check for that. When you're done with the core memory and other buffers, call sim_free_bufs(void), but note that sim_alloc_bufs() frees any memory it previously allocated. i.e. you can successively call it without intervening calls to sim_free_bufs(). For backward compatibility you can also use use sim() as follows to allocate with the default CORESIZE=PROCESSES=8000: (void) sim( -1, 0, 0, CYCLES, &core ); ii) Then you clear core and load the warriors: memset( core, 0, sizeof(insn_t) * CORESIZE ); or sim_clear_core(); and sim_load_warrior(0, &(w1.code), w1.len ); sim_load_warrior(pos, &(w2.code), w2.len ); where `pos' is the core address where you want to load w2. This differs slightly from the previous release where you had to: memcpy( core, w1.code, sizeof(insn_t) * w1.len ); memcpy( core + pos, w2.code, sizeof(insn_t) * w2.len ); You can still do that. (Note however, that the flags field of instructions should be cleared as the simulator won't do it anymore. sim_load_warrior() does this for you. You can get the old behaviour by defining the preprocessor variable SIM_STRIP_FLAGS to 1 at compile time.) iii) Right, now you're ready to fight. If you have only one or two warriors, you can use sim(): result = sim( 2, w1.start, (w2.start+pos)%CORESIZE, CYCLES, NULL); The result is 0 if w1 won, 1 if w2 won, and 2 on a tie (-1 if the simulator panics). For multi-warrior battles with more than two warriors you need some extra declarations: field_t war_pos_tab[ NWAR ]; /* a table of core addresses * that specify where the warriors * start their execution. */ int death_tab[ NWAR ]; /* the simulator stores the * results here. (see below) */ Then allocate the buffers and obtain the core address using sim_alloc_bufs(), clear the core, and load the warriors into it as before. Save the positions where you loaded the warriors into war_pos_tab. Now you're ready to fight, so call sim_mw(): int alive_cnt; alive_cnt = sim_mw( NWAR, war_pos_tab, death_tab ); The return value is the number of warriors alive at the end of the round. Dead warriors are recorded into death_tab by order of death. i.e. death_tab contains the index of the first warrior to die, death_tab the index of the second to go, and so on. * The disassembler: If you want to disassemble an instruction use dis1() or discore() from asm.c. Examples: #include "exhaust.h" #include "asm.h" char str; insn_t instr; instr.in = _OP( SPL, mF, DIRECT, BPREDEC ); instr.a = instr.b = 0; core = instr; dis1( str, core, CORESIZE ); printf("%s\n", str); dis1( str, instr, CORESIZE ); printf("%s\n", str); /* disassemble addresses 5..14 */ discore( core, 5, 15, CORESIZE);/* and print to stdout. */ * P-Space --------- The simulator interface is oriented toward playing single battles, but p-space by its nature requires a multi-round interface. Hence there are some complications when playing p-space warriors. The main change for P-spacer battles over normal ones is that you need to pair p-spaces to warriors. Also, if you are running multiple battles you need to reinitialise the p-spaces between each battle. The basic model is simple: the simulator maintains an array of NWARRIORS p-spaces that you may manipulate. The ith p-space in this array always corresponds to the ith warrior passed to sim() or sim_mw(), so that the first warrior always accesses the first p-space, the second the second, and so on. The pairing betweens p-spaces and warriors is up to you. After each round the p-space location 0 of each warrior is updated with either a) zero if the warrior died or b) the number of warriors alive at the end of the battle. [A note about naming conventions: pspace_XXX() functions are mostly local in nature -- they refer only to their argument p-spaces. The sim_XXX_pspace() functions are about the collection of p-spaces maintained by the simulator.] * Accessing p-spaces To get a pointer to the ith p-space you use the function sim_get_pspace(): pspace_t *p = sim_get_pspace(i); Then to access the cells of `p', use the functions pspace_get() and pspace_set(): contents_of_cell_123 = pspace_get(p, 123); pspace_set(p, 123, new_contents); * P-space initialisation; resets - The size of p-space is specified at memory allocation time using the function sim_alloc_bufs2(). It is exactly like sim_alloc_bufs(), except it takes as an additional parameter the size of pspace, which must be positive. The later function defaults p-space to CORESIZE/16 cells, so you don't need to change it. - The function sim_clear_pspaces() clears the p-spaces of all warriors and stores CORESIZE-1 into their p-space location 0. P-Space is cleared on allocation, so you only need to call this function between battles, but not rounds. This function doesn't change the sharing status of p-spaces. - To completely reinitialise p-spaces and to make p-spaces of each warrior private again, call the sim_reset_pspaces() function. This also calls sim_clear_pspaces(). * Pairing p-spaces to warriors If you are changing the order in which warriors execute within each cycle on a per-round basis, you need to order p-spaces as well. For example, pMARS cyclically permutes the order of warriors so that every even round the first warrior executes first in each cycle, and every odd round the second warrior executes first. With non-pspace warriors you only need to give the starting positions of the warriors in the `war_pos_tab' argument in the order you want the warriors to execute, but with p-warriors involved you need to match p-spaces to warriors as well. - The simulator maintains an array of pointers to p-space structures, one for each warrior. The ith p-space structure corresponds to the ith warrior in the `war_pos_tab' array argument of warrior starting positions passed to sim_mw(). Similarly for the two warriors given to the sim() function. You need to order this array of pointers to match the order in which you are passing warrior starting positions to the simulator. To access the array, use sim_get_pspaces(): pspace_t **pspaces; pspaces = sim_get_pspaces(); // now permute the pointers in the pspaces array into the // same order as your warriors. ... Of course, if you are not changing the order in which warriors execute on a per-round basis, you don't need to permute the pspaces array. This may be suitable in some cases. * Sharing p-spaces There is only minimal support for PINs and p-space sharing in that the assembler understands the `PIN' pseudo-op, but you must match PINs to p-spaces yourself. - The warrior structure warrior_st in exhaust.h needs the extra fields int have_pin; // boolean: is field `pin' valid? u32_t pin; // PIN of warrior, possibly. that are used when assembling from files/streams. The interface to asm_line() has changed accordingly so it can identify and return the PIN pseudo-op. - To share p-spaces of two warriors, you need to call the function pspace_share(p1, p2) for two pointers p1, p2 to p-spaces. The contents of p-space p1 becomes the contents of the shared p-space. P-space location 0 is never shared. - The function pspace_privatise(p) resets the p-space p to be private. The contents of p1 are then undefined. * Files that the assembler eats ------------------------------- In short: the load files that the '94 draft specifies are accepted. Specifically, the files may contain only `;' comments, white space, instructions, and pseudo-ops. The instructions must follow this form: [opt START label] OPCODE.MODIFIER MODE integer , MODE integer Various idiosyncrasies: - You can't implicitly specify direct addressing mode by dropping `$'. - The start of a warrior is determined by the last seen appearance of the START label or `ORG'. Of the pseudo-ops using `END label' or `ORG label' aren't supported; only `ORG integer' is. - `END' stops warrior assembly. You still can assemble from the same file or stream another warrior that follows though. - special KotH comments aren't recognised. To support using pMARS as an assembler any line that begins with `Program' is ignored. So you could first say pmars -r 0 Fancy_Source.red >Simple_Source.rc and then use the asm.c assembler to import Simple_Source.rc into your program. Note that pmars doesn't output PINs of warriors. * When the simulator crashes ---------------------------- ... or is just plain wrong by producing incorrect results please tell me about it at the address given below. Ditto for the assembler or anything else in these files. I'd appreciate a simple test case that illustrates the bug if possible. The author can be reached by email at: email@example.com