SHENZHEN I/O

SHENZHEN I/O

196 ratings
MCxxxx Reference Card 2.0 (WIP)
By robin!
(WIP) There are a lot of hidden commands, as well as nuance to the ones that aren't hidden. This is meant to be a more comprehensive Reference Card (RTFM first!) to supplant the one in your documentation. Suggestions and translations welcome!
2
6
2
   
Award
Favorite
Favorited
Unfavorite
Intro
Need a faithful companion to reread as you code? This guide ONLY covers the commands, instructions, and other basic programming elements - no walkthroughs, no solitaire, nothing about projects - but contains a lot more useful information about these things, all put in one place for easy access. Hopefully it will help you on problems without giving away anything (anything spoilery is marked as such).

Authorial note: This is a work in progress, and it definitely could benefit from your help! Names in italics after lines in the command list are people who have contributed to the guide in that line. In the explanation section, direct quotes will be used with credit given. Credits will also be given at the end.
Before You Begin: The Basics
If you're writing code, in-game or not, you can and should probably skip ahead to the Commands section - this is here for people who are totally lost. This info is also in the manual and best enjoyed that way, but I'm going to try to make it easier to read for those who are overwhelmed.

THE BASICS (aka WHAT THE HELL IS THIS):


Don't feel bad if you're here! While projects can be easy or not, if you don't know these basics the whole game is impossible. Take as long as you need to familiarize yourself with these ideas; they will be the foundation of everything you do here.

Let's start with the terms:

Terms
A module is any component you put into a design. These can be really wide and varied, but to start, you can think of them as independently operating parts in your machine. Some modules just do one thing (you can't write instructions for them), but most of the modules (and especially the ones you'll be thinking about) can be given a small number of instructions that you write yourself.

Modules are meant to work together (very few projects work without your modules working together!), but each one acts on its own simultaneously with all the others, so your job is largely to help them communicate and translate information received into instructions or data to send. In order to do this, you will mainly be writing commands and connecting modules to one another. Modules communicate using pins.

Pins are the outside parts of the modules that you can run wires to in order to connect them. They do all the work of transmitting and receiving information, and some pins can do both. For any module you can write to, what happens at the pins depends on what you wrote.

A command is any instruction you give to a module. These are best thought of as simple functions, and generally appear one per line, followed by the things the module needs to know to execute that instruction. Commands will be listed with their required parameters, followed by some notes on their use.

The command list format looks like this:
<command> <parameter> <parameter2> <parameter3> <etc>
<command> is the name of the basic instruction, usually three letters that begin a line of code.
<parameter> is a required item for the instruction to run. There can be multiple parameters.

Parameters include these:
  • R - A register.
    Registers are pointers to specific values a module can access.

  • I - An integer.
    Integers, for the purposes of this game, are all whole numbers from -999 to 999, 0 included. Your modules can't handle numbers larger or smaller, much less decimals or fractions, so don't even try. (This may be frustrating at points - there are ways to deal with this!)

  • R/I - Either a register or an integer.
    Most arithmetic (math) commands use this format, so you can use a constant number (such as 123) or a variable number (specific values that may or may not change, but that the module can access).

  • P - A pin register.
    This is a specific type of register that points to a value incoming or outgoing from a module at one of its pins.

  • L - A label.
    You can label lines in your program. Do this by starting the line with the name of your label, followed by a colon (the symbol : ). Most often, you will use labels to direct the module to jump to a specific part of its program and run from there.

About Pins
Until you get going, let's begin with the two biggest pin types you'll see:
  • Simple input/output (I/O) pins (all of which start with the letter p)
    These are the easiest pins to explain - they can be used to write (send) or read (receive) data. If a module writes to one of these, it sends an integer value onward to the connected module until something changes that. They don't forget as long as they aren't given other instructions - write a value to an I/O pin and it can and will keep sending that data forever. You can use the same pins to read these values coming from another module - when they receive a value from a connected pin, they read it and offer a piece of information for their module. The biggest problem with I/O pins is they can't do both at the same time; they will either be reading or writing, and while they can switch between these, when a I/O pin does one action, the other stops.

  • XBus pins (all of which start with the letter x)
    XBus pins can also write or read data, but they have an different style of doing that. Instead of the way an I/O pin sends or receives data constantly until instructions change, XBus will write a value only once, to be read by another XBus pin only once. You might be asking, why would I use XBus pins? The first and best reason is to send an instruction that starts an action on another module. When you tell an XBus pin to write, it will send out the value given, but won't do anything until the value is received; when you tell an XBus pin to read, it will wait for a value to appear before moving on. This allows you to effectively make a module wait until another asks for info (or sends the requested info). The big drawback to XBus is tied to this advantage - it can wait to send or receive FOREVER. If nothing asks for its data or gives it what it asked for, it will do nothing, and that module will stop working - this can cause your module to essentially "lock up", where it can't and won't do anything more, and halts the function of the entire design. As a result, make sure that for every time you write to an XBus pin, another pin will read it (eventually), or things will quickly fall apart.

Let's talk about one last thing, and the other biggest reason your early designs might fail:

Sleep
It's necessary to tell the modules to sleep.A module that you write any instructions for must include a way to sleep.

Why sleep?

The long version: modules are essentially super-small computers, and like computers, can execute (at the very least) thousands of instructions per second. While this can sound awesome, for our tiny computer, it's also a major danger. For every instruction a module processes, it uses power, and if nothing tells it to hang on once in a while, it will consume power endlessly without limit. In the real world, if a computer were to do this, the best description of what would happen is immense power use, followed by the slightly more dramatic catching fire from the amount of heat all that power would give off. This game doesn't describe it in these terms, but for this reason, a module that doesn't sleep or stops sleeping will result in project failure.

Fun aside: if you omit this action from a module, you might notice that your power draw goes from a reasonable amount (exactly how many commands the module runs before it sleeps, including ones it repeats) per time unit to an astronomical amount (almost immediately in the millions, and note that the next time unit never comes). This would continue in the real world until there was no power left or until your design caught fire and stopped working (because few things work really well while literally on fire). As an exercise to the reader, drop your own snarky line about Samsung devices here.

In short: if a module doesn't sleep, your design doesn't work. There are different ways to make a module sleep, which will be covered in the commands section.

Summary
You will be placing modules, connecting them, telling them what to do, reminding them to sleep, and (finally) making them perform a function that the design instructions specify.

Last, a friendly reminder: you can play this game if you're not a programmer (you might become one in the process, though)!
Command List
Note: these are the commands on the Reference Card included with the game/game files, with minor annotations.

Basic Instructions - The Essentials
nop
Does nothing. Good placeholder for a line you're not sure about. Can also be used to synchronize modules by how many lines they execute - nop still counts as a line.
mov R/I R
The most essential command, this makes a module write a new value to a register. First parameter is what it reads from, second is what it writes that value to.
jmp L
The first, best way to tell your module to skip to another spot. L is a label you've written on a line in the module. Remember that labels can come before the rest of a code on a line - you don't need to have "JUMPHERE:" as a line all by itself, even though the manual showcases this style more than once in example programs. (jeff)
slp R/I
Tells your module to sleep for a number of time units, equal to either a number you write or one that it can read from a register. Remember, a module that can't sleep will break your design.
slx P
Another way of telling your module to sleep, this makes it wait (including passing time units) until a new value comes in over a certain pin register. Excellent when you want a module to sleep until another one is done working.

Test Instructions - The Conditionals
teq R/I R/I
Checks to see if the first value is equal to the second. If so, it will execute lines that follow beginning with +; if not, it will execute lines that follow beginning with -. Has no effect on lines without prefixes.
tgt R/I R/I
Checks to see if the first value is greater than the second. If so, it will execute lines that follow beginning with +; if not, it will execute lines that follow beginning with -. Has no effect on lines without prefixes.
tlt R/I R/I
Checks to see if the first value is less than the second. If so, it will execute lines that follow beginning with +; if not, it will execute lines that follow beginning with -. Has no effect on lines without prefixes.
tcp R/I R/I
Arguably the most versatile conditional - compares the first value with the second. If the first is greater, it will execute lines that follow beginning with +; if the first is less, it will execute lines that follow beginning with -. Note that it will not execute any of the + or - lines if the two values are equal.

Arithmetic Instructions - The Calculators
add R/I
Adds a number to the register ACC, and writes the result over ACC (either a value you specify or the value read at another register).
sub R/I
Subtracts a number from the register ACC, and writes the result over ACC.
mul R/I
Multiplies ACC by a number, and writes the result over ACC.
Note: there is no command offered for division on the card. Get creative!
not
Reads ACC; if ACC is zero, writes 100 over ACC, but if ACC has any other value, writes 0 over ACC. Useful to clear values, as well as to provide a response when ACC is 0.
dgt R/I
Reads ACC's individual digits (disregards negative/positive) - the value you specify is the digit it reads. This digit will then be written to ACC. Digits are represented 2, 1, and 0, and they refer to the hundreds, tens, and ones digit of ACC, respectively (if ACC is smaller than 100 or 10, those digits will read as 0). Example: if ACC is -27, its digits are 0 2 7. dgt 2 would write 0 to ACC; dgt 0 would write 7 to ACC. The result will never be negative.
dst R/I R/I
Reads ACC's digits, chooses the digit specified by the first value (see above), and rewrites the digit from the second value to ACC. If ACC is 594 and the module executes "dgt 1 7", it will pick the tens (dgt 1) digit and change it to 7, rewriting ACC to be 574.

Registers - The Pointers
ACC
Our best friend. All MCxxxx modules have this register, and it is the often the only read/write internal value. If you need to put a number somewhere, here's your first option.
DAT
Our best friend's other bestie. The MC6000 has this register (others may or may not) - it provides a valuable second storage spot for integers. You cannot use any of the arithmetic commands directly on DAT, so don't expect this register to be easy to calculate with.
NULL
Have an XBus write you need to toss in the garbage? Writing to NULL completes another module's write request without actually doing anything with the value. This can be useful when combined with slx to make an XBus signal function as a wake-up for a module. (jeff)
p0, p1, ...
Simple I/O pins. Can read a number in from or write a number out to these.
x0, x1, ...
XBus pins. Can read a number in from or write a number out to these. If there isn't a module eventually sending to/receiving from this pin, a command including an XBus register will never complete. Until that module makes the connection, the current module will stop and wait.

These are all the reference card commands. More may come as you play the game.
Hidden Command List (spoilers)
Don't read ahead if you don't know what you're looking for; these could spoil developments that should occur naturally.

All of these are under spoiler tags, along with a note to let you see if you've already uncovered this command. Notes about them follow. BE WARY!

First, have you read the manual? You really should do that. And then again if you don't know something; there's a lot more in there than meets the eye.

In The Manual

Most of what you'll find in the manual not covered earlier in this guide is related to either specific projects or specific modules not available at the beginning of the game. While no new commands can be gleaned from the manual (correct me if you find one), you will see reference to other pins, specifically a1 and d1 (for the read-only memory and random-access memory modules that come later in the game). That said, reread it regularly - there are a lot of pieces that are invaluable later that won't make sense early.

Pins
a1, a2, ...
Address pin for RAM/ROM modules. Reading from this pin will give the current address of the active memory cell. Writing to it will change the active cell to the one indicated by the value written.
d1
Data pin for RAM/ROM modules. Reading from this pin will give the data value of the active memory cell. Writing to it on a RAM module will overwrite that cell with a new value; writing to it on a ROM module will have no effect, and if writing over XBus, will block the module writing.


IN COMPANY E-MAILS
Much new information you find about commands will come out of reading the company e-mails you receive. They are not simply filler (with possible exception of the Shenzhen Days mailing list) - pore over them to find new tidbits that will change your game.

E-mails will be listed by subject line - if you don't have this e-mail, you will probably spoil something for yourself. You've been warned!

UNDOCUMENTED INSTRUCTION!
gen P R/I R/I
Generates a on/off (100/0) pulse on a simple I/O pin. Indicate the pin first, then the time for it to broadcast 100, followed by the time for it to broadcast 0. Setting the first duration to any positive integer makes it broadcast 100, but also makes the module sleep for that length of time. Setting the second duration to zero still changes the value broadcast to 0, but does not make the module sleep. Setting both to zero is the same as mov P 0.

@ (prefix)
A line preceded by @ will only run once. As far as I can tell, there is no way to make this contingent upon anything else; for example, you can't make it run only once in a loop every time the loop gets called, because it will only run once the first time the module reaches that line, and then NEVER again. It will not rerun even if the module finishes instructions and wraps around to the beginning.


More coming as I progress further.
Commands Explained
This is a longer, discussion based form of the Command List. Here, we'll be talking about neat tricks and shorthand that can make your code trimmer or perform useful functions. It will still follow the list format, though.

Have a neat trick? Want to see your name in a Steam Guide? Let me know and you'll be credited!

Basic Commands
nop
Nope, or No Operation? Either way, a great placeholder and consumes 0 power. Great if you want to see if you have enough room to execute that magnificent idea you dreamt up.
jeff says: "A possible use for nop is to slow one microcontroller down until another one can set a simple I/O signal which you want to read. Without the nop you might end up reading the signal before it gets set, meaning you read an out of date value."

mov R/I R
Sometimes it feels like every other line is mov. It's that essential, but also, just really annoying in how much space it takes up. If at all possible, when multiple (esp 3+) mov to the same pin exist in a module, set up a label and remove the needless extra lines.

jmp L
jmp is great, but overuse can happen. Instead, try positioning your code to minimize the number of jmp necessary to make things work as they should. In general, if anything follows another thing, put it below that. jmp should only be employed when you Need to go backward. If you feel you need it to skip past certain lines, can those lines be made conditional?
jeff adds an important reminder: "You can have a label on the same line as an instruction, like `label: mov 50 p0`. That way you don't have to spend 2 lines to set up a jmp."

slp R/I
Sleep is absolutely necessary and slp is a simple command; however, it is a potentially dangerous one too. If you don't time your slp lines correctly, it can shutter an otherwise functional project. Consider using slx whenever you can rely on a XBus output - much less calculation involved in terms of "what is the exact duration and start time should this module sleep".

slx P
Another needed command, coupled with another danger. slx is best employed when you either have a module connected to one of the common early "unblocking radio transceiver" modules, or when you have it connected to something that makes a regular, expected XBus output. The former can't block, and the latter makes synchronizing XBus reads and writes a breeze. When a line with slx hangs, you know the problem is with whatever should be writing to the connected XBus pin.

Test Commands - The Conditionals
teq R/I R/I
teq is a reliable tool. If you're waiting for a specific moment to happen with one exact trigger, teq is fantastic. If you're unsure what the exact input you want to respond to will be, use another conditional. teq is also fantastic for defining loop conditions; where you might have used for() in a C based language to achieve this end, teq is a natural replacement.

tlt R/I R/I
tgt R/I R/I
tlt and tgt are good for defining ranges. Make special note when you need one value to work and the integer one above/below not to - if you want to respond at 20 (or higher) but not 19, tlt 20 and tgt 19 are equivalent. Pay attention especially when the design is asking for greater/less than versus greater/less than or equal to - the "equal to" will usually result in you needing to change the tgt/tlt check value by one. Also remember the first value is the one that is being described as greater than/less than - tgt (20) (19) will enable + instructions, because it is true that 20 is greater than 19.

tcp R/I R/I
This conditional can be used often in the place of two others, saving you lines in the process. For example, if I want to jmp X at value 1, jmp Y at value 2, and jmp Z at value 3 (inefficient, but just for this hypothetical), "tcp R/I 2" will enable + statements when R/I is 3, - statements when R/I is 1, and neither when R/I is 2. Therefore, you could follow up "tcp R/I 2" with "- jmp X", "+ jmp Z" and "jmp Y", knowing that the last line will only be executed if the value was precisely 2. It becomes a three-way conditional as a result.

Arithmetic Instructions - The Calculators
add R/I
sub R/I
Not too much to say here, aside from noting that this always uses ACC as the base and also the location to which it writes the result - if you need the original value of ACC still, either store it in DAT or move it somewhere else. Ideally, you might find a clever way to avoid needing the original ACC intact, or even a way to avoid using the add or sub commands - they do end up taking a lot of space.

mul R/I
dgt R/I
dst R/I R/I
For the most part, mul is unremarkable - except for the fact that you cannot divide. If you need to execute division (and I would urge really examining whether you NEED that), you may be best served by combining mul with dgt/dst.

For example, let's say my input is a multiple of 75, from 0 to 300 inclusive, and I really just need which value out of the five possible - {0, 75, 150, 225, 300} - it is, expressed as a single digit. Where it would be really convenient to be able to say "div 75", we need another way to write the same idea; here one might try "mul 2" followed by "dgt 0". Why? Because when multiplied by two, these values become {0, 150, 300, 450, 600}. Each of these has a unique hundreds digit - 0, 1, 3, 4, 6. Multiplying avoids the problem that 0 and 75 have the same hundreds digit (0). Note that this does not yield a precise division, but rather a unique state identifier in a single digit, which is useful when you want to use a single mov to communicate multiple variables; for example, sending 205 when each digit 2, 0, 5, represents the state of a different thing. Less variables means less lines, fewer transmits and less needed storage.

Registers - The Pointers
ACC, DAT
When you can only perform arithmetic on ACC, you may find yourself pulling out hair on how much time it takes to work with ACC and DAT mathematically. There are some simple pointers, related to what you might use during this process.

Commands Explained, Part 2
Continuing from Part 1...

mov ACC DAT
There are times when this line is necessary, but there are so many times I have used it when I didn't need to. Consider a few things:

First, are you taking a value from simple I/O pins or XBus pins? Avoiding something like "mov p0 ACC", "mov ACC DAT" is ideal - if the value is still the same in p0, you have ready access to it until it changes. Consider striking a line, dependent on whether you need to perform arithmetic on it or store it in static memory. If you're reading from XBus, you've only got one chance to capture the value, so you do need either a mov command or an add/sub/mul (XBus pin) command to ensure it is somehow stored. Remember, using conditionals on XBus values counts as a read, and the XBus value disappears after.

Second, are you moving this because ACC is about to change, and there's nowhere else to access the value? If so, there may be an alternative. An ideal situation would be a situation where you can figure out how ACC will change and simply account for the meaning of final result. If multiple things are acting on ACC, but you can figure out by the end result what happened, you can avoid storing in DAT. If possible inputs are +5, -4, x7, for example, what would you make of an end result of any number ending in something other than 5 in ACC? If ACC was at 0 and it isn't still there, something triggered -4 - there's no way to start at zero, only add 5s and multiply by 7s, and end at anything but a multiple of 5. These may take a little ingenuity on your part, and it's way easier to do this if you write out a list of possible combinations of inputs (how many times can each operation happen? what are the different variations that can result?) and then what they would sum up to. Better still, changing what values get sent forward to communicate things helps tons here - sending 100 for event A, 10 for event B and 1 for event C tells you exactly how many times each happened in a 3 digit number, without guesswork. Do you need a precise calculation, or a status report? Ask yourself this before beginning.

(More coming soon!)
Hidden Commands Explained (spoilers)
Continuing on Hidden Commands, but with more in depth examination. Spoilers may follow.

From the Manual

From Company E-mails
These are again listed by where you could first have seen them, so don't look if you haven't gotten that e-mail yet.

UNDOCUMENTED INSTRUCTION!
gen P R/I R/I
gen is a really useful but often touchy function, specifically when you need to use the sleep components of it. gen saves lines, certainly, but your primary concern is this - do I know how much I need it to sleep, and if not, can I find out?

The pin you use must be a simple I/O (p0, p1, etc). Remember that that four events will happen on use of a gen line:
  • The pin specified is set to 100.
  • The module sleeps for the duration of the first value.
  • The pin specified is now set to 0.
  • The module sleeps for the duration of the second value.
The reason to establish what is probably obvious is this: gen is safest to use when you can set the second value to 0. It completely eliminates the fourth event, while still resetting the pin to 0 after the pulse is over. If you don't have to set a sleep time after, you won't have to worry about lining up timing for XBus functions to/from the module except for the period you know it will be both sending the pulse and asleep - one period of time. If you need that extra sleep duration, remember to add them up and check and doublecheck that the nearby modules are timed appropriately so nothing is waiting on this module during it's sleep cycles.

@ (prefix)
I'm still ambivalent about this useful and yet quite limited line modifier. As noted earlier, @ marks a line as to be executed only once - for our purposes, this seems to be limited to "only once per simulated test run". As a result, putting @ in a loop doesn't work so well - a conditional may be used to restrict when it activates, but once it has, it never will again for that simulation. This leaves @ as a fantastic way for starting a module and ill suited to literally anything else.


(More to come soon.)
Final Comments
Aside from my own notes and those credited to other contributors, all data is taken from Shenzhen I/O, in-game and PDF manual information, and its publisher/owner is to be considered the only copyright holder of any of the text within.

All other contributors are to be listed here with their contributions:
jeff (http://cs2bus.com/id/jeffreydav)
- Reminded me to both discuss the null register and mention that labels don't need their own line.
- Gave an alternate use for nop (to synchronize module line execution/ensure that a direct I/O read is as up-to-date as possible).



Thanks to Zachtronics for another great game, and thank you for reading. If you have any suggestions or corrections, please leave a comment on the guide.

-- eskies, 14 Oct 2016
@emeraldskies


This guide may be republished elsewhere if left fully intact, including this section.
22 Comments
Colonel Atomic 17 Dec, 2023 @ 4:37pm 
One of the most userful tricks for me is using the fact, that integer X that you write to address pin of memory nodes will actually become X%14. And the fact that if you use 0, 1, 10, 11, 100, 110 and 111 as an addresses it will points to different cells.
I remember how I realized this fact 5 years ago when tried to optimize billboard task for better lines and it changed my life.

PS: there is a lot more cool tricks that can change your game experince not described here. Just be creative ;)
KingOfJacks 15 Jan, 2023 @ 8:59am 
for another purpose for @, consider programs where you have to sleep at the end of a cycle and jump to the beginning. if your program has enough branches, you can save space by putting the slp as the very first instruction of your program, except now, you might mess up the first tick of your program, therefore, use @ to jump over it on the first tick.

example:
@ jmp start
loop: slp 1
start: teq p0 100
....etc

starting at 3 jumps (explicit or implicit), you save instructions.
Jaguar 30 Jan, 2022 @ 9:00pm 
The @ decorator is most useful in projects where you need to set some initial data. For example: a simple output's value needs to start at 50, or a project requires a countdown timer in ACC that starts out enabled (therefore above 0).
Priscilla (Laila Sandra) 22 Jul, 2021 @ 2:52pm 
@stormageddon
That's a breakpoint in the code; yes, you can set breakpoints in the code as well (not just on the oscillator graph timeline thingy (dunno how it's called)).
danman 12 Apr, 2020 @ 9:55am 
I like to think of tcp doing an XOR comparison: https://en.wikipedia.org/wiki/Exclusive_or

It's TRUE, if the values are different, and FALSE, if they're equal. The effect it has on the following ± lines depends on if the left or right operand was greater. In other words:

Left OP is larger => + lines are executed
Right OP is larger => - lines are executed
Otherwise (meaning: equal) both are ignored.
Lstor 19 Mar, 2020 @ 9:03am 
2.5 years later, I just wanna point out that nop is 'no operation', not 'nope' (:
Dronezilla 31 Jul, 2017 @ 1:37pm 
The @ conditional becomes a lot more useful when you realize that code execution wraps from last line of the module to the first line without requiring an explicit "jmp" instruction. This means that if you design your module around one big loop that enters at the very top, and make clever use of the +/- conditionals, you can often avoid using "jmp" instructions at all! The @ conditional is key to making this work by letting you put your init code inside the big loop without having to jump over it somehow.
matusz 19 Jul, 2017 @ 7:21am 
the dst command has some quirks:
1. you may use bigger numbers (2 or 3 digits) and only the number of ones will be used, (it means for 'dst 1 456' the result will have middle digit set to 6) (but look at point 2.)
2. the sign of the resulting number is set to the sign of the number passed as second parameter
3. you may pass both arguments from inputs, but not from the same input, does not matter if they are xbus or simple (ie. you may write 'dst x0 x1' but not 'dst x0 x0')
4. if you use any number besides 0,1,2 as the first parameter - it will get ignored without error

for dgt : the operation preserves the sign
matusz 19 Jul, 2017 @ 7:06am 
The conditional lines do not need to be continuous, and do not need to appear immediately after the condition. It even works after back-jumps
for instance you can write:

L1: - add 1
L2: tcp acc p0
L3: mov x1 acc
L4: -jmp L1
L5: slp 1
L6: +sub 1

the hidden comparison register will not change until the next time any conditional instruction executes
Miral 31 Dec, 2016 @ 1:25am 
Ctrl-Click (not Alt-Click) sets a breakpoint. It mentions this in the tooltip on the verification tab, but it works in the CPU instructions as well.