A custom CPU, a ring shaped memory and instruction pointer relative addressing. Two programs enter, one program leaves. A deadly low level programming competition to overwrite your opponents memory.
In contrast to the original Corewars our custom CPU has some modern features, such as registers. We provide a debugger that allows to singlestep and inspect your fighters execution in the core. If you need to test your bot against another bot, there is a profiler that allows you to perform many battles in short amount of time. Additionally, the package contains an assembler / disassembler and a competition server as well as a web interface that displays the results of all battles. Lastly, for all the vim users out there, we included a custom vim color syntax definition.
Welcome to a new generation of CPUs! We run on 48 bits, using 4 registers, relative addressing, cyclic memory, and last but not least a customized instruction set.
The Gentoo handbook told me once:
- READ EVERYTHING, don’t skip around. don’t skim the text, but do read the whole thing once to get an idea whats going on.
- Use your brain! Stop and think about what its saying.
- If you have a problem, try to solve it yourself.
The same goes for you.
The memory, (which will be called: board through this manual) will be initialized with 0s. Then the CPU will load the programs, and start them in an arbitrary order. The CPU uses cyclic relative addressing. Therefore all addresses are relativ to the Instruction pointer. mov *1 *0 will move the cell at IP+0 to the cell at IP+1 (thus effectivly copy the instruction to the next cell). Furthermore the memory is cyclic (addresses that are bigger than the biggest valide address are wrapped). Assuming a Coresize of 400 cells, trying to read from address 403 will result in reading address 3. A program never knows its “real” address, as the current instruction will always have the address 0.
The general layout of the supported instructions is:
name_of_instruction destination source
if you feel like it you can also write:
Well, you get the idea. I will only use the first version, because it uses
less characters. Additionally, it is possible to write comments everything
after a semicolon will be ignored:
;Hi dear reader, I am a comment
same goes for //
//I am a comment, too!
You can use the special comments:
;desc: here goes some description of your bot, to provide your bot with meta information
Every memory cell (read: “every memory word”) consists of 3 parts:
[op1] [op2] [cmd] 20 20 6 Bits
While it is possible to use operands that are not needed by the instruction, it is considered bad style and will most probably result in unexpected crashes. Do not use additional operands for modifying additional values by means of post decrement/increment, because this would result in ugly code.
There are four general purpose registers. ptr - the pointer register acc - the accumulator register r1 - nameless register one r2 - nameless register two
There are three flags that will be set by the CPU:
- equal - will be set if the result of the last instruction was zero
- above - will be set if the result of the last instruction was >zero
- smaller - will be set if the result of the last instruction was < zero
The flag will be set for every instruction where it makes sense ;) For example cmp, all arithmetic and logic operations
- mov dest src - move stuff from the source to the destination
- mvp val unused - move stuff to the *ptr register
- mva val unused - move stuff to the acc register
- xor dest src - dest xor src, the result will be saved to dest
- and dest src - dest and src, the result will be saved to dest
- or dest src - dest or src, the result will be saved to dest
- not dest unused - binary not on destination, the result will be saved to dest
- dec dest unused - decrement destination, save the result to dest
- inc dest unused - increment destination, save the result to dest
- neg dest unused - negate destination, save the result to dest
- add dest src - add src to dest, the result will be saved to dest
- sub dest src - sub src from dest, the result will be saved to dest
- mul dest src - multiply dest and src, the result will be saved to dest
- div dest src - divide dest by src, the result will be saved to dest
- mod dest src - the remainder of the division dest/src, the result …
- ica unused unused - increment acc
- dca unused unused - decrement acc
- ada val unused - same as add acc val
- sba val unused - same as sub acc val
- jmp dest unused - continue execution at dest
- jie dest unused - jmp if equal
- jia dest unused - jmp if above
- jis dest unused - jmp if smaller
- jne dest unused - jmp if not equal
- jna dest unused - jmp if not above
- jns dest unused - jmp if not smaller
Important and useful other stuff
- cmp src dest -compare the src and the destination, flags will be set
- splt dest unused - create a new thread at dest
- die unused unused - stop execution of this thread
The CPU has an interrupt vector containing three interrupts. The cpu has a hidden register containing the interrupt flags. If any of this flags is set, the cpu will jump to offset stored in the interrupt vector. If two or more interrupts are set, one instruction is executed in the first interrupt handler, than the next interrupt handler is called (you can use this instruction to disable the interrupt_enable flag). The interrupts are prioritised as following: First call Imem, then cza and finally cza.
- sti unused unused - enable interrupts
- cli unused unused - disable interrupts
- rti unused unused - return from interrupt (does not work recursively)
The next instructions are used to set the interrupt vectors. If the dest is 0 this interrupt is disabled
- clk cycles dest - jumps to dest every #cycles, the #cycles must be >=4
- mem addr dest - jumps dest whenever addr is changed
- cza val dest - jumps to dest if acc is equal to val
You can use labels to increase readability of your code. :label - everything after a “:” will be a label (only [a-z] are allowed for labeling! jmp @label - will jump to label it is also possible to use @label in other context, just know that @label holds the relative addr of :label
$constant=42 - defines a constant, with name constant and value 42, you can now use it whenever needed (e.g. add ptr, $stepsize+2)
a parameter looks like:
mov r1 **3-
The binary layout of the parameters:
[type][value_type][value][op] 2 1 16 2 Bit type: 01=> direct (*ptr) 10=>indirect(**ptr) 11=> immediate (ptr) 00=> invalid value_type: 1=>reg / 0=>constant value op: 01=>inc 10=>dec 11=>noop 00=> invalid
While writing your code you can use two different tools:
Debugger First of all, use
ruby debugger.rb $yourbot $otherbotto visualise the behaviour of the bots. This program is a graphical cpu emulator. Commands:
- Space - pauses/restarts emulation
- Arrows / hjkl - move the cursor (the value under the cursor is shown in the lower part of the screen)
- -/+ - increase/decrease the simulation speed
- q - exit emulation
- n - abort current simulation and restart
- s - single step through emulation
- m - moves the cursor to the next running thread
- t - cycles which threads registers are displayed in the lower line
Profiler Then evaluate the efficiency of your bot with
ruby profiler.rb $yourbot $otherbot. This program will perform a huge number of games and tells you about the outcome.
So you have written your Code, what now?
You will have to send the code the the server. To do that you have to use the bot_add script.
The server will have a scoreboard running on
http://server:8080/scores.html you will also find the parameters of the game on top of the scoreboard (e.G. max number of threads per player, coresize etc.)
ruby bot_add.rb $yourbot $server $username $password
The server the tests the performance of your bot. The results will be shown on
For the challenge version you have to use bot_add.rb $yourbot 10.11.200.3 $password. The password is obtained by completing either of the two challenge branches. Each challenge branch will give you two passwords. either send $pass1$pass2 or $pass2$pass1 as password for your bot.
install Ruby (1.8 and 1.9 work) & ruby-dev install ruby gems install ruby1.9.1-dev (fixes mkmf error) install the ncurses, json(only needed for 1.8) and the parallel gem (gem install ncurse && gem install parallel) (maybe you have to install the packge ruby1.8-ncurses or something like this) => everything should work now (note, ncurses seems to be buggy - even the samples don't work on some machines -_-)