TOY

Input
Output

TOY4

Hz
0
ADDR / PC (0)10
0 0 0 0
#
0 0 0 0
#
DATA | INSTRUCTION (0)10
0 0 0 0
#
0 0 0 0
#
0 0 0 0
#
0 0 0 0
#
REGISTER
ADDR #0 #1 #2 #3 #4 #5 #6 #7 #8 #9 #a #b #c #d #e #f
MEMORY
TinkerTOY ← Tinkern + TOY + JavaScript
TOY Instruction Format
4 bits4 bits4 bits4 bits
opdst
opdst (addr or data)
1 byte
TOY Operations and Associated Tinker Methods
TOYTinkerDescription
#0---HALT(Stop a running program.)
#1---ADDR[d] ← R[s] + R[t]
#2---SUBR[d] ← R[s] - R[t]
#3---ANDR[d] ← R[s] & R[t]
#4---XORR[d] ← R[s] ^ R[t]
#5---SLR[d] ← R[s] << R[t]
#6---SRUR[d] ← R[s] >>> R[t]
#7---LDAR[d] ← st (memory address)
#7---LDBR[d] ← st (a byte of data)
#8---LDR[d] ← M[st]
#9---STM[st] ← R[d]
#a---LDIR[d] ← M[R[t]]
#b---STIM[R[t]] ← R[d]
#c---BZif (R[d] == 0) PC ← st
#d---BPif (R[d] > 0) PC ← st
#e---JMPPC ← R[d]
#f---JMPLR[d] ← PC; PC ← st
Auxiliary Tinker Methods
MethodDescription
R(i, byte)byte ? R[i] ← byte : R[i]
M(i, byte)byte ? M[i] ← byte : M[i]
PC(addr)addr ? PC ← addr : PC
IR(byte)byte ? IR ← byte : IR
TOY()(Goto Tinker Toys vs. Lego.)
/** Program IO (Input output). ** by John P. Spurgeon **/ "use strict"; // Opt into strict mode. const FA = "5b"; // First Address const BA = "5e"; // Branch-to Address const toyProg = [ "80ff", // 5c (FA): R[0] <- M[ff] "d0" + BA, // 5d: if R[0] > 0 PC <- BA "0000", // 5e: halt "90ff", // 5f (BA): M[ff] <- R[0] "d0" + FA, // 60: PC <- FA ]; storeProgram(FA, toyProg.length, toyProg); Tinker16.PC(FA); function storeProgram(firstAddr, n, prog) { const base = 16; let currAddr = parseInt(firstAddr, base); for (let i = 0; i < n; i++) { const cmd = parseInt(prog[i], base); Tinker10.M(currAddr, cmd); currAddr++; } }
/** Program S (Store). ** by John P. Spurgeon **/ "use strict"; // Opt into strict mode. const FA = "50"; // First Addr const BA = "55"; // Branch Addr const HI = "5a"; // addr of Halt Instruction const toyProg = [ /* A TOY program that reads from standard * input an address at which to start * storing values in memory followed by * a count of the number of values in an * array of values followed by the values. * The values are stored in memory in * sequential order beginning at the * specified address of the first value. * * R[0]: 0 (zero, a constant) * R[1]: 1 (step size, a constant) * R[a]: addr to store next value at * R[c]: count of values remaining * R[f]: value most recently read in */ "7000", // 50: R[0] <- 0 "7101", // 51: R[1] <- 1 (step size) "8aff", // 52: R[a] <- M[ff] (addr) "8cff", // 53: R[c] <- M[ff] (count) "cc" + HI, // 54: Goto HI if R[c] is 0 "8fff", // 55 (BA): R[f] <- M[ff] (value) "bf0a", // 56: M[R[a]] <- R[f] (value) "1aa1", // 57: R[a] <- R[a] + R[1] "2cc1", // 58: R[c] <- R[c] - R[1] "dc" + BA, // 59: Goto BA if R[c] > 0 "0000" // 5a (HI): halt ]; storeData(FA, toyProg.length, toyProg); Tinker16.PC(FA); function storeData(firstAddr, n, values) { const base = 16; let currAddr = parseInt(firstAddr, base); for (let i = 0; i < n; i++) { const val = parseInt(values[i], base); Tinker10.M(currAddr, val); currAddr++; } }
/** Program B (Boot). ** by John P. Spurgeon **/ "use strict"; // Opt into strict mode. const FA = "00"; // First Addr const BA = "06"; // Branch Addr const TC = "0b"; // Transfer Control addr const toyProg = [ /* A TOY program that reads from standard * input an address at which to start * storing a program in memory followed by * a count of the number of instructions * in the program followed by the * instructions. This program (Program B) * stores the other program (the program * being loaded) in memory in sequential * order beginning at the specified * starting address and then transfers * control to the other program by setting * the PC to the starting address of the * program just loaded. * * R[0]: 0 (zero, a constant) * R[1]: 1 (step size, a constant) * R[a]: addr of next instruction * R[b]: boot addr (addr of first value) * R[c]: count of values remaining * R[f]: value most recently read in */ "7000", // 0: R[0] <- 0 "7101", // 1: R[1] <- 1 (step size) "8aff", // 2: R[a] <- M[ff] (addr) "1ba0", // 3: R[b] <- R[a] + R[0] "8cff", // 4: R[c] <- M[ff] (count) "cc" + TC, // 5: Goto TC if R[c] is 0 "8fff", // 6 (BA): R[f] <- M[ff] (value) "bf0a", // 7: M[R[a]] <- R[f] (value) "1aa1", // 8: R[a] <- R[a] + R[1] "2cc1", // 9: R[c] <- R[c] - R[1] "dc" + BA, // a: Goto BA if R[c] > 0 "eb00" // b (TC): PC <- R[b] ]; storeProgram(FA, toyProg.length, toyProg); Tinker16.PC(FA); function storeProgram(firstAddr, n, prog) { const base = 16; let currAddr = parseInt(firstAddr, base); for (let i = 0; i < n; i++) { const cmd = parseInt(prog[i], base); Tinker10.M(currAddr, cmd); currAddr++; } }
/** Store Program C (Compare). ** by John P. Spurgeon **/ // The following IIFE (immediately invoked // function expression) stores a TOY program // in memory. It takes a starting address // as input and returns the address that // immediately follows the address of the // last instruction stored. (function storeProgramC(A0) { console.log("0. Input: #" + A0); // STEP 1. Opt into strict mode. // // Evaluating the string "use strict" may // affect how a particular JavaScript // interpreter treats the JavaScript code // that follows. Many JavaScript programmers // consider evaluating "use strict" at the // beginning of a program to be a best // practice, regardless of the algorithm // being implemented. On the one hand, we // don't need to be too concerned about how // evaluating "use strict" affects this // program, since it has no bearing on the // conceptual algorithm. On the other // hand, it can affect the rules we are // obliged to follow when implementing any // algorithm in JavaScript. RTFM. "use strict"; console.log("1. Opted into strict mode."); // STEP 2. Define address labels. const A = { a: stepHex(A0, 4), // (Begin x <> y). b: stepHex(A0, 8), // (Find MSB of XOR). c: stepHex(A0, 12), // (Found MSB of XOR). d: stepHex(A0, 15) // (End x <> y). }; console.log("2. Address labels:\n" + A); // STEP 3. Define the TOY program. const toyProg = [ /* A TOY function that takes as inputs * two values representing non-negative * integers, x and y, and outputs a value * indicating whether x = y, x > y, or * x < y. * * R[#1]: #0001 (a constant) * R[#2]: #000f (a constant) * R[#3]: R[#d] & R[#e] * R[#4]: R[#a] & R[#e] * R[#a]: input: x * R[#b]: input: y * R[#c]: output (#ffff, #0, or #1) * R[#d]: R[#a] ^ R[#b] * R[#e]: a variable bit mask * R[#f]: return-to address */ // INPUTS // // R[#a]: An unsigned integer, x. // R[#b]: An unsigned integer, y. // OUTPUT // // R[#c]: #0000 if x = y; or // #0001 if x > y; or // #ffff if x < y. // LABEL A0 (Start address). // INSTRUCTION 1 (A0 + 0): // We presume x = y. If we discover that // that is false, then we'll update R[#c]. // Recall that a zero in R[#c] indicates // that x = y. "7c00", // R[#c] <- #00 // INSTRUCTION 2 (A0 + 1): // We determine whether any of the bits // in x (R[#a]) differ from the bits in // y (R[#b]) by XORing x and y. If x = y, // then the XOR value will be 0. // Otherwise, the XOR value will be > 0. "4dab", // R[#d] <- R[#a] ^ R[#b] // INSTRUCTION 3 (A0 + 2): // If XOR value > 0, then x <> y; in that // case, go to A.a and determine which is // greater, x or y. "dd" + A.a, // if R[#d] > 0 PC <- A.a // INSTRUCTION 4 (A0 + 3): // If XOR value = 0, then x = y as // initially presumed. The algorithm // terminates by transferring control // to the instruction located at the // address loaded in R[#f]. "ef00", // PC <- R[#f] (return address) // LABEL A.a (Begin x <> y). // INSTRUCTION 5 (A0 + 4): // We need the constant value 1 in a // register. We'll use the value in // subsequent instructions. "7101", // R[#1] <- #01 // INSTRUCTION 6 (A0 + 5): // Now we presume x < y. Recall that the // value 0 - 1 (i.e. #ffff) indicates // that x < y. Note that the value of // R[#c] is #0000 (from INSTRUCTION 1), // and the value of R[#1] is #0001 // (from INSTRUCTION 5). "2cc1", // R[#c] <- R[#c] - R[#1] // INSTRUCTION 7 (A0 + 6): // We put the decimal value 15 (#0f) in a // register so we can use it to form the // value #8000, i.e. 1000000000000000 in // base 2. The value #8000 is the initial // value of a bitmask that we'll use to // find the most significant bit (MSB) of // the XOR value of x and y. That MSB is // important, because it is the first bit // from the left that differs in x and y // and will therefore tell us whether x // or y is larger. The larger value will // have a 1 in that position, while the // smaller value will have a 0 there. "720f", // R[#2] <- f (shift left amt) // INSTRUCTION 8 (A0 + 7): // R[#e] <- 1000000000000000 (bitmask) "5e12", // R[#e] <- R[#1] << R[#2] // LABEL A.b (Find MSB of XOR). // INSTRUCTION 9 (A0 + 8): // Apply bitmask to R[#d], which holds // the value of x XOR y. If the result // is greater than zero, then we will // have found the MSB. Otherwise, we // will keep searching. "33de", // R[#3] <- R[#d] & R[#e] // INSTRUCTION 10 (A0 + 9): // If masked value > 0, then found MSB. "d3" + A.c, // if R[#3] > 0 PC <- A.c // INSTRUCTION 11 (A0 + 10): // Otherwise, shift bit mask value one // place to the right. (Recall that the // value of R[#1] is 1.) "6ee1", // R[#e] <- R[#e] >> R[#1] // INSTRUCTION 12 (A0 + 11): // Continue searching for MSB: go back // to LABEL A.b. "c3" + A.b, // if R[#3] = 0 PC <- A.b // LABEL A.c (Found MSB in R[#d]). // INSTRUCTION 13 (A0 + 12): // Is MSB a 1 in x or y? To find out, // we AND the bitmask in R[#e] with the // value of x in R[#a]. If the result // equals 0, then value of the MSB is // 0 in x and 1 in y; otherwise, the // MSB is 1 in x and 0 in y. "34ae", // R[#4] <- R[#a] & R[#e] // INSTRUCTION 14 (A0 + 13): // If MSB of y, then x < y as presumed. // Jump to A.d (End x <> y). "c4" + A.d, // if R[4] = 0 PC <- A.d // INSTRUCTION 15 (A0 + 14): // Actually, x > y. Our revised // presumption was wrong too, so we // update register #f with the correct // final answer. (It's not a presum- // tion this time; it's a fact.) "7c01", // R[#c] <- #01 // LABEL A.d (End x <> y). // INSTRUCTION 16 (A0 + 15). // The algorithm terminates. We yield // control to the instruction at the // address stored in register #f. "ef00" // PC <- R[#f] (return) ]; console.log("3. TOY prog:\n" + toyProg); // STEP 4. Note the program size. const progSize = toyProg.length; console.log("4. Program size: " + progSize); // STEP 5. Store the TOY program in memory. storeProgram(A0, progSize, toyProg); console.log("5. Stored program."); // STEP 6. Compute the address in memory // following the last instruction stored. const nextAddr = stepHex(A0, progSize); console.log("6. Next address: #" + nextAddr); // STEP 7. Return the value of nextAddr. console.log("7. Output: #" + nextAddr); return nextAddr; /** Utility Functions **/ function storeProgram(firstAddr, n, prog) { const base = 16; let currAddr = parseInt(firstAddr, base); for (let i = 0; i < n; i++) { const cmd = parseInt(prog[i], base); Tinker10.M(currAddr, cmd); currAddr++; } } function getLastTwoDigits(twoOrMore) { const sliceAt = twoOrMore.length - 2; return twoOrMore.slice(sliceAt); } function stepHex(addr16, stepSize) { const addr10 = parseInt(addr16, 16); const newAddr10 = addr10 + stepSize; const newAddr16 = newAddr10.toString(16); const digits16 = pad(newAddr16, 2); return getLastTwoDigits(digits16); } function pad(digits, minLength) { while (digits.length < minLength) digits = "0" + digits; return digits; } } // storeProgramC ends here. // Pass the value of the starting address // as an argument to the IIFE. )("60"); // A0 <- "60"
/** Store Program M (Mystery). ** by John P. Spurgeon **/ // The following IIFE (immediately invoked // function expression) stores a TOY program // in memory. It takes the starting address // of the program and the starting address // of a function as input and returns the // address that immediately follows the // address of the last instruction stored. (function storeProgramM(P0, F0) { console.log("0. Inputs"); console.log(" P0: #" + P0); console.log(" F0: #" + F0); // STEP 1. Opt into strict mode. // // Evaluating the string "use strict" may // affect how a particular JavaScript // interpreter treats the JavaScript code // that follows. Many JavaScript programmers // consider evaluating "use strict" at the // beginning of a program to be a best // practice, regardless of the algorithm // being implemented. On the one hand, we // don't need to be too concerned about how // evaluating "use strict" affects this // program, since it has no bearing on the // conceptual algorithm. On the other // hand, it can affect the rules we are // obliged to follow when implementing any // algorithm in JavaScript. RTFM. "use strict"; console.log("1. Opted into strict mode."); // STEP 2. Define address labels. const A = { a: stepHex(P0, 2), // (?). b: stepHex(P0, 6), // (?). c: stepHex(P0, 10) // (?). }; console.log("2. Address labels:\n" + A); // STEP 3. Define the TOY program. const toyProg = [ "7101", // P0 + 0 "7a00", // P0 + 1 "8bff", // P0 + 2 (A.a) "db" + A.b, // P0 + 3 "9aff", // P0 + 4 "0000", // P0 + 5 "ff" + F0, // P0 + 6 (A.b) "1cc1", // P0 + 7 "dc" + A.c, // P0 + 8 "4abc", // P0 + 9 "da" + A.a // P0 + 10 (A.c) ]; console.log("3. TOY prog:\n" + toyProg); // STEP 4. Note the program size. const progSize = toyProg.length; console.log("4. Program size: " + progSize); // STEP 5. Store the TOY program in memory. storeProgram(P0, progSize, toyProg); console.log("5. Stored program."); // STEP 6. Set the PC. Tinker16.PC(P0); console.log("6. PC <- " + P0); // STEP 7. Compute the address in memory // following the last instruction stored. const nextAddr = stepHex(P0, progSize); console.log("7. Next address: #" + nextAddr); // STEP 8. Return the value of nextAddr. console.log("8. Output: #" + nextAddr); return nextAddr; /** Utility Functions **/ function storeProgram(firstAddr, n, prog) { const base = 16; let currAddr = parseInt(firstAddr, base); for (let i = 0; i < n; i++) { const cmd = parseInt(prog[i], base); Tinker10.M(currAddr, cmd); currAddr++; } } function getLastTwoDigits(twoOrMore) { const sliceAt = twoOrMore.length - 2; return twoOrMore.slice(sliceAt); } function stepHex(addr16, stepSize) { const addr10 = parseInt(addr16, 16); const newAddr10 = addr10 + stepSize; const newAddr16 = newAddr10.toString(16); const digits16 = pad(newAddr16, 2); return getLastTwoDigits(digits16); } function pad(digits, minLength) { while (digits.length < minLength) digits = "0" + digits; return digits; } } // storeProgramM ends here. // Pass the value of the starting address // as an argument to the IIFE. )("70", "60"); // P0 <- "70", F0 <- "60"
/** Program 0 (Playful and serious). ** by John P. Spurgeon **/ /* .:....1....:....2....:....3....:....4.... * Mathematics and computer science * can be both playful and serious * at the same time. * * - DONALD E. KNUTH, Selected Papers on * Fun & Games (2011) * * * The lowercase Greek letter λ (lambda) * is an unofficial symbol of the field * of programming language theory. This * usage derives from the lambda calculus, * a model of computation introduced by * Alonzo Church in the 1930s ... * * - EN WIKIPEDIA, Programming language * theory (accessed 11 Aug 2018) */ "use strict"; // 0. Opt out of sloppy mode. // .:....1....:....2....:....3....:....4.... // 1. First, we define a string to print; it // will be one thing or another depending on // the value returned by Math.random. Then // we record the length of the string (i.e. // the number characters in the string). const str = Math.random() > 0.5 ? "l. Programming\n" : "m. Mathematics\n"; const n = str.length; // .:....1....:....2....:....3....:....4.... // 2. Now we form a list of the unique // characters in the string we selected in // section 1 above. (Sorting the characters // in the string helps us identify any // characters that appear more than once in // the string. The sort method we are using // operates on arrays, so we use the split // method to convert the string to an array // of characters before calling sort.) The // algorithm we employ to filter duplicates // is concise, but it requires just a little // bit of effort to understand. Clues are // provided in the comments below. A more // detailed explanation and/or proof of the // correctness of the algorithm is left as // an exercise for the reader. const uniqueChars = []; // An array. const sortedChars = str.split("").sort(); for (let i = 0, j = 0; i < n; i++) { // Is the value in sortedChars at index // i different from the value at index j? // If so, then set the value of j equal to // the value of i. if (sortedChars[i] !== sortedChars[j]) j = i; // If the values of i and j are equal, // then add the character in sortedChars // at index i to the end of uniqueChars; // otherwise, do nothing, because we must // have already added that character. if (i === j) uniqueChars.push(sortedChars[i]); } // .:....1....:....2....:....3....:....4.... // 3. We should make sure there aren't too // many distinct characters in our string, // in case someone changes the program // and tries to use it to print something // like "The quick brown fox jumped over // the lazy dog." TOY (TOY4, that is) has // only 16 registers, and this peculiar way // of printing requires every unique // character to be stored in a register // simultaneously. const m = uniqueChars.length; if (m > 16) { let e = "Too many distinct characters!"; e += "\nUse a more flexible algorithm "; e += "or at most 16 unique characters." throw Error(e); } // .:....1....:....2....:....3....:....4.... // 4. We gather information about each // unique character in the string. For each // character, we need to know its numeric // code, and we need to specify the number // of a register in which to store the code. const charInfoSet = {}; // A map. for (let i = 0; i < m; i++) { const char = uniqueChars[i]; const code = char.charCodeAt(); const info = {code: code, reg: i}; const id = code.toString(); charInfoSet[id] = info; } // .:....1....:....2....:....3....:....4.... // 5. Now we create our ultimate data // structure: a sequence of character // information objects corresponding to the // sequence of characters in the string we // want to print. const charInfoSequence = []; for (let i = 0; i < n; i++) { const id = str.charCodeAt(i).toString(); charInfoSequence[i] = charInfoSet[id]; } // .:....1....:....2....:....3....:....4.... // 6. Now we can assemble the machine code. // We specify the location in memory where // we'll start loading the TOY instructions; // we set the program counter (PC) to that // address; we load the TOY instructions // that will load character codes into the // registers; then we load the instructions // that will write out the string by storing // each character code, one at a time, in // TOY's last memory address. const progStartAddr = 0; Tinker10.PC(progStartAddr); for (const id in charInfoSet) { const info = charInfoSet[id]; Tinker10.LDB(info.reg, info.code); } for (let i = 0; i < n; i++) { const next = charInfoSequence[i]; Tinker10.ST(next.reg, 255); } // .:....1....:....2....:....3....:....4.... // 7. Finally, we load the essential HALT // instruction, which will presumably be // used to stop the program, and we set // the PC (the program counter) to the // address of the first instruction of the // program to make it easy to get the // program started via TOY's Run button. Tinker10.HALT(); Tinker10.PC(progStartAddr);
/** Program 1 (Salutations Thing 1). ** by John P. Spurgeon **/ "use strict"; // Opt into strict mode. // 1. Name the constant variables. const thing1 = "Thing 1"; const phrase = '"Hi, ' + thing1 + '!"\n'; const n = phrase.length; const stepReg = 0; const addrReg = 1; const dataReg = 2; const stepSize = 1; const lastMemAddr = parseInt("ff", 16); const dataStartAddr = parseInt("20", 16); const endOfString = 0; // 2. Load the data (character codes). let addr = dataStartAddr, data; for (let i = 0; i < n; addr++, i++) { data = phrase.charCodeAt(i); Tinker10.M(addr, data); } Tinker10.M(addr, endOfString); // 3. Note where the instructions begin, // and move the cursor to the beginning. const progStartAddr = addr + 1; Tinker10.PC(progStartAddr ); // 4. Load the instructions. Tinker10.LDB(stepReg, stepSize); Tinker10.LDA(addrReg, dataStartAddr); Tinker10.LDI(dataReg, addrReg); Tinker10.BZ(dataReg, Tinker10.PC() + 4); Tinker10.ST(dataReg, lastMemAddr); Tinker10.ADD(addrReg, addrReg, stepReg); Tinker10.BP(dataReg, progStartAddr + 2); Tinker10.HALT(); // 5. Reset the cursor. Tinker10.PC(progStartAddr );
/** Program 2 (Salutations Thing 2). ** by John P. Spurgeon **/ "use strict"; // Opt into strict mode."use strict"; // Opt out of sloppy mode. // 1. Name the constant variables. const thing2 = "Thing 2"; const phrase = "'Hi, " + thing2 + "!'\n"; const n = phrase.length; const stepReg = 0; const addrReg = 1; const dataReg = 2; const stepSize = 1; const lastMemAddr = parseInt("ff", 16); const dataStartAddr = parseInt("38", 16); const endOfString = 0; // 2. Load data (character codes). let addr = dataStartAddr, data; for (let i = 0; i < n; addr++, i++) { data = phrase.charCodeAt(i); Tinker10.M(addr, data); } Tinker10.M(addr, endOfString); // 3. Note the address of the first // instruction and assign it to the PC. const progStartAddr = addr + 1; Tinker10.PC(progStartAddr ); // 4. Load instructions. Tinker10.LDB(stepReg, stepSize); Tinker10.LDA(addrReg, dataStartAddr); Tinker10.LDI(dataReg, addrReg); Tinker10.ST(dataReg, lastMemAddr); Tinker10.ADD(addrReg, addrReg, stepReg); Tinker10.LDI(dataReg, addrReg); Tinker10.BP(dataReg, progStartAddr + 3); Tinker10.HALT(); // 5. Reset the program counter (PC). Tinker10.PC(progStartAddr );

About TOY

  lly  Rabbi
 i  \ V /   T
S   /. .\    !
  ==\ T /==
    / R \
   a  I  e
 for  X  kids.