 If this video goes out on time, which it might not, it should be the 50th anniversary of the Apollo 11 moon landings. I thought I would commemorate the occasion by writing a very simple lunar landing game on my new compiler. And what better hardware to actually run a lunar landing game on than an actual lunar lander? Now, I say an actual lunar lander, but I don't actually have one. There's only like one left, and it's in deep space. But I do have virtual AGC, which is a superb simulation of the Apollo guidance computer used by the LEM, the lander itself, and also by the command module. They made, I think, 75 of these, and all but two are now defunct. Quite a lot of them crashed into the moon, some of them crashed into the earth, one of them is floating in deep space, one of them has been lovingly restored and is now running for real, and all the rest are non-functional parts in museums. The Apollo guidance computer itself was a massive 40 kilogram chunk of material, mostly handmade. It made out of discrete logic components, integrated circuits, very early ones, all hand-wired together using wire wrapping. It did not have a silicone memory, it used magnetic core RAM. This is a section of magnetic core rope, a variant of the technology used for the ROM, so this is non-volatile. I don't have a picture of the actual RAM itself. It was a fascinating, incredibly expensive and utterly bizarre machine, and it is going to be fun to port to. If you're used to modern computers, the AGC is like the weirdest thing ever. Computers were different back then, and they hadn't settled on the architecture used by modern machines. So we have, you know, 8-bit bytes. The AGC used 15-bit words, except when it didn't, sometimes it used 16-bit words. We have computers with registers. The AGC didn't really, depending how you count, it either has hundreds or three or none. Modern computers used two's complement arithmetic. The AGC didn't. The AGC used one's complement arithmetic, except when it didn't and used two's complement arithmetic. Modern computers run at a fixed clock rate. The AGC didn't. A lot of the logic was written using asynchronous logic, and it just sort of ran at an approximate speed. It just got on with stuff until the logic was done. An instruction will be documented as taking, you know, about 23 microseconds. And the instruction set itself is utterly bizarre. I've gone through it and it's got all the bits I need to make my compiler work, but I am actually doing this for real. This is not a rigged thing. I haven't done any of the work yet. I've put together a skeleton compiler code generator. See? No code. And I'm just going to fill stuff out. So this is going to be a learning experience for, like, everybody. Oh yes, I should also mention that modern computers have, like, display terminals. The AGC had this. This is a disky. It's the IO device used, the astronauts communicated with the computer. They would type in commands using the keypad here and read the results here. The only way the computer had a talking to them was flashing lights and showing numbers. Yeah, the AGC did not do text. Like, not at all. There is no ASCII in the binary ever. Everything is done using numbers. So, suppose we should better get started. So, our compiler. Well, I wrote this thing in under a week. I mean, literally I did the prototype on video on Saturday. So, I've done a ton of work on it since then. So I've factored out all the code generation stuff. But it's still pretty cheap and shoddy and full of bugs. So this is going to be a bit of a learning experience. In fact, this morning I had an insight which would allow me to drastically simplify, make it faster, flexible, easier to understand and produce better code. But I haven't had a chance to do that yet. So what you see is what we got. So we might as well get started, I suppose. So we built the program. Now that's built the compiler. And we've got a test program which is test.cow here, which is empty. I can run it, run the compiler and it fails, of course. Now, don't want to load that one. I want to load that one. Our compiler is going to generate assembly code that we're going to feed into a assembler called yahoo. This is a modern replica of the original Yule assembler, written in 1959, which read programs of punched cards and during an overnight assembly, which job would produce a binary, that people would then have to hand weave into this stuff. They did actually have, like, core rope simulators, so you could develop without doing that. Yahoo is a pretty close replica of the original. It takes the same syntax. Its intended purpose is to be able to read the original source code for luminary and colossus, the two programs used by the LEM and the command module, and turn them into binaries that we can tell are the same as the ones that were loaded into the actual computers of the day. So the other syntax is somewhat archaic, and we're limited to stuff like, you know, eight character identifiers and there isn't a linker and lots of really weird stuff. So we're going to have to work around some of that. I have put together a skeleton program, which is this. This contains things like the interrupt initialization code, the code to nerf the watchdog timer, which interestingly they called the night watchman back then. What this does is it repeatedly checks to see if this memory location has been read from, and if it hasn't, it assumes the computer's crashed and resets it. And we don't really care about that, so we use one of the timers to just read from it periodically. So we use YARULE to assemble our machine code. This then produces a binary file, you know, 74k. That's how big it was. And we can show you the hex. It's, you know, not much for a program. And we then run that in the emulator. So the first thing we're going to have to do then is to write out these two files. This is a modern extension to YARULE, which allows include files, and that's so, so useful. So what we're going to do is code file, data file. Let me do that way around. And this is going to be a bodge job. So cow.data.agc, cow.code.agc. And we don't actually need to do any other work here just yet, because our skeleton does it all for us. And we'll get on to the actually interesting bit in a moment. So, okay, that has actually failed a bit further on, which is just what we want. We want to have generated a blank data file and a blank code file, which is good. Okay. Now, what we're going to do first is we're going to try and assemble and run this program, which is empty. This will give us the minimum viable product for a compiler. And then we go and add features to that. I do not actually need my 8080 code generator anymore. So let's just close that. So, this seems to be a good time to talk about the ICER, because we're about to have to start touching it. From my perspective as a modern application writer, the AGC has three registers known as A, L and Q. There's a fourth register, Z, which is the program counter, but that's internally. The registers are in fact all memory mapped. So A is at address zero, L is at address one, and Q is at address two. There's... These are special in that they are 16 bits wide rather than 15, which the rest of the system is. They use the 16th bit to check for overflows. Now, there are lots more registers, but I'm just going to think of those as being memory mapped I.O. The computer does not have anything resembling stack pointer. Now, that's fine because CalGol, the language which I am actually compiling here, forbids recursion. So we know that functions will not be called re-entrantly, and therefore we don't need an idyllamic stack at all. Each function's workspace can be statically allocated in memory, which is very useful. So what do we do when we enter a subroutine? Well, this is where the Q register comes in. The AGC opcode for calling subroutines is TC, and what this does is it pushes... thinking about stacks too much, puts the return address into the Q register, and then the return instruction jumps to the address in the Q register. I believe that the original authors just didn't use nested subroutines very much. However, we are, so what we're going to have to do is, in our prologue, save the Q register into memory, and then in the epilogue, load the return address out of memory into the Q register, and then do the return. To do this, we are going to have to start emitting code. So we are going to write a simple function to actually emit things. And I'm going to steal this, so this is going to be emit data. Actually, do I want to do that? No, I don't. I can just use printer if I don't need that at all. So the prologue is going to be... Well, we're going to need to emit a label for the subroutine itself. So something like... We're then going to need to emit the code to save the Q register. And I was just thinking that the UL syntax has labels left justified and code justified in. So rather than actually put lots of spaces in, I'm actually just going to do define code, pen space, just do that. So I can do... If this is code, I can just say, I want to store Q in the return address. Now, the instruction to store, there are several, they're all weird. So TS, transfer to storage. TS writes A, the accumulator, into memory location. It's also a jump instruction. If A, which remember is a 16-bit register because it's got an overflow bit, if A overflows, it skips the instruction after the TS. So this is supposed to allow you to do stuff like TS, VAR, TCF, transfer control to fixed address, that's a jump instruction. It's supposed to... Hang on, have I got that backwards? Nope, this would actually be much more sensible. This allows you to do this, which to be honest, I find a slightly odd way around to doing it. Now, I don't care at all about overflow, but I do care about the code randomly skipping instructions. So I'm going to have to be really careful when I use TS that I haven't just done arithmetic because if I've done arithmetic, the overflow bit might be set and therefore it might skip the next instruction. So yeah, however, we can't use this here because TS only stores the A register. So in fact, we are going to use... What's it called? QExchange. QExchange, if I can actually find it, swaps the value of Q with the value in a memory location. Now, this will corrupt Q, but we don't actually care about that. So QExchange, return storage. And then here in our epilogue, we're going to do QExchange, return storage, return, like so. And in fact, I hate this. I'm going to go back to my original plan and we're going to put in some helper functions. Print f, this is code, so code fp, print f, code fpsup, eLabel, which will be exactly the same but without the indentation. Put some new lines in too. And eData. Now, eData is going to be slightly different because all data values will be a label and an instruction. So we're actually going to do... trying to remember how to do left justified strings. Let's try that and see what happens. Okay, so now our code here can be simply emit label, label, emit code, QExchange, return storage, QExchange, return storage, return. That does not work because... Okay, so what has this produced for our code? That wasn't quite what I expected. Okay, 20 is way too much. Oh no, no, this has actually worked. So what we are seeing is one side. So that's a compiler bug. I think I did a bad merge. Yep, I don't want that there. And I'm also calling prolog twice. Oh no, I'm not. Yes, that is actually correct. So what we've got is the subroutine label. We save Q to return storage. We load Q from return storage and we return. So that is working. Now we now need to actually emit the appropriate label. So that is actually just current sub name and we stick a leading underscore on it just to disambiguate it. Okay. Again, I'd be careful in the code because in our programs, because we have to keep it under seven letters. We'll deal with that in a moment. But now we need to allocate some data. So this will happen in the epilogue. We say emit data. Yeah, I have in fact. This is just, this is screwed up. We actually want, we actually want all the same stuff here. That's ugly. We can fix the names in a moment. E-data label. Return storage. E-data arrays. Now arrays is a special instruction that causes the assembler to skip one word. We just use this to allocate one's words worth of storage. So what does this do? That has not changed our code at all. But our data now contains a single item of data called return storage. And of course return storage is illegal. It's too long. Right. This is still not working. So yes, we are going to have to deal with names. So rather than actually use the name of the subroutine here, we're just going to put a numeric identifier in. So that's just going to be current sub ID. And subroutine is here. You add an ID field initialized to zero to our code generator. And every time we start a subroutine, we simply allocate it a new ID. So subroutine 1, S1. So this then allows us to do. So this should actually be a working program. It doesn't do much. So let us try assembling it. Nope, I didn't like it. Oh, yeah. Going to have to tweak our program slightly. So we need to find a few variables. So when the system boots, it will call startup, which will relint releases the interrupt flag, basically enables interrupts. We call our main subroutine, which is here. When the main subroutine returns or we jump to exit, we simply go into an infinite loop and keep poking the night watchman. Otherwise, the system will reboot. And we don't need this ease anymore. However, required extend is missing. This brings us to another interesting point about the AGC instruction set, which is they extended it a number of times so they ran out of space. So they ended up adding this extend instruction. This allows two word opcodes. The assembler ought to omit extend itself when you call QExchange. I mean, it warns you that you need it. But they never got around to implementing it. Therefore, your Yule being an honest replica of the original Yule also does not do it. So this actually means that each of these is two words long, which is a bit of a shame. So that is our new program and it assembles. Let us load it into the... That wasn't what I was expecting. So, yes, sorry about that. I found a bug. So it turns out that if you put the label and instruction on different lines like this, then Yule accepts it fine, but then the actual simulator crashes. So you see that loads fine. And if I... It's produced the same binary. So what's actually happened is the AGC simulator has failed to parse the symbol table. So Yule is generating invalid symbol table. Well, that's nice. That means that we're actually going to have to, you know, do some more work here. So we have to omit the label and the instruction on the same line. And this actually breaks a couple of assumptions of the generator core. So what we're going to do is we're simply going to cache the label in a buffer. Just give it a random size for now. And... So calling label will simply set the buffer and we're going to check to make sure that we don't omit two labels in a row because that won't work. And then when we omit a instruction of code, we are just going to stick the label in the front. So that actually simplifies our model quite a bit. So that's nice. All right, so label. Let's try this. Okay, this is just like boilerplate. And yep, I have in fact used the wrong modifier here. Dot, I think. Okay, I'm just going to have to read the instructions. We want to left justify minus. Yeah, printf is great. It's so intuitive. That's better. Okay, now we are going to assemble it. Now we are going to run it in simulator. Oh, good. So we should be able to put a breakpoint on S1. Run the program. I can show you the address. Show you the value of Q. So the AGC does everything in octal. GDB over here, or at least AGC's rather poor simulation of GDB. This is not GDB itself. It does everything in hex. So that's convenient. So I can simply step to there. I can then examine contents of Q1, which is what we expected. Step again, return address. Q hasn't changed. We return to somewhere. Yeah, we are actually at the address we wanted, which should be here. Yes, we're at exit. I don't know why it hasn't actually shown me a line number. Yeah, I think the line numbering is a bit wrong. I suspect it hasn't taken these and include directives into account. Yep, and it just spins forever. So we now have our first actually working program. Good stuff. So we can now emit labels. So our arch emit label is going to be easy to do, and our arch emit jump is also going to be easy to do. The jump instruction, as I mentioned, is TCF. In general, instructions which end in an F mean fixed memory. Fixed memory is the AGC's terminology for ROM. Some instructions will only work on a raiseable memory, some will only work on fixed memory. Some of them are really confusing. But yes, just done that. So, oh yes. Label alias is generated by the compiler core when it wants to make one label the same as another label. And this is actually... The syntax is slightly different. So we want to do... This is generated for doing things like a sequence of conditional operations because they're hard to do in a single pass. I've just done that because they were there. So let's go to our program here and let's actually create some code. Ah, we can't actually do any code. We don't have any types. Let's make a type. Now, remember when I said that this machine used 15-bit values? Well, our single type is going to be number type int 15, not an int 16, occupying one machine word and it's signed. Now, the machine does actually support int 30s, occupying two machine words, but I'm not going to actually worry about that right now. Make number type here creates a new type and searches into the global system table. And we also have to tell the compiler that this is the type generated by a pointer comparison. So that's all that does. So now we can go back to our program and do int 15. Okay, what does this do? That has worked fine because it's generated no code. Yes, because we haven't done anything with the variable, it hasn't actually generated any code to, you know, place it anywhere. Now, remember that I was saying that CalGoal does not support recursion. So it doesn't need a proper stack. Where a normal stack-based language would have a stack frame, we have a thing called the subroutine workspace, which is just a block of memory somewhere. And one of the things we need to do in our epilogue is actually allocate space for the workspace. And we use the W variable, the W identifier, with the subroutine ID to do that. And we, again, use arrays to allocate memory. But this time you have to tell it how much. And this is the subroutine workspace. But because Yule is special, it's actually one less than the workspace. So we can see here that we've actually allocated one word plus zero extra words. Let me double-check that because it's really unintuitive. Arrays n, skips n plus one bytes of a raiseable memory. Okay, where n is an octal or decimal integer constant. I hope that means it takes a preceding zero if it's octal, otherwise that would be weird. So when we get to higher values, I should actually check that. Okay, so we can now allocate variables. Let's try and put a number in one. What does this do? Okay, now we get on to the actual nitty-gritty of the code generation. The code generation system used here is to use a stack. We don't have a stack, so we are going to fake one. Now, the way we do this is we actually have to keep... We're going to keep a... Let me think about this a moment. Yes, we are going to keep a stack pointer. This will indicate how much stack we've actually used, and we're going to have to create a couple of functions to do pushes and pops. Now, for the 8080 code generator, I had to remember what it was I was pushing and popping. I think I don't need to do that. No, I am going to have to do that. I need to track the... Yeah, I need to know whether I've pushed constants that can be then rematerialized later. No, I don't. No, I don't, actually. Okay, right, so when we do a push, all we do is increase the stack pointer. However, each subroutine has to keep track of how much stack it's actually used. So simple. So we are trying to push a constant code gun. So this is simply going to load a value from the constant pool and put it onto the stack. So what we do is we set the way I want to do it. No, we're not going to do it like that. We're going to do... So this will increment the stack pointer and return the old one. So when we push our constant... Yes, what we're going to do is load a constant from the constant pool and this is done using the CA instruction. Now CA means clear and add. What it does is it clears the A register. It loads the value pointed to by its operand and adds it to the cleared A register. The naming is slightly weird and the constant pool has to live in fixed memory, so that's going to be... We'll get onto what the constant pool is in a moment. So that will load it into memory. We then need to write it to our stack slot. Now we can use TS for this because this has just been read out of 15-bit memory, therefore it cannot overflow. So... And let's just do... Now it wants to write it to a variable. So the sine var. Now this will pop a value off the stack and again... So our code is going to be... We wish to load the value off the stack frame and then we want to write it to our variable. Again, we've just loaded it from memory, therefore TS is safe. Okay, what does that do? Right. Now the code is terrible but it should... If I've got everything right, it should work. So what this has done is... The AGC instruction set does not allow you to actually embed literal constants. So what we're going to do is we're going to have to store the constant value 1 in fixed memory and this C0 is going to point at that. So CAF will load the value in C0 which is going to be 1 into the air register. TS is then going to store it onto our virtual stack. The virtual stack belongs to subroutine 1. I should use a different letter for that. Actually, let's change this to F for function. That's better. S1 is going to be a block of memory that we will allocate for this subroutine's virtual stack and 0 is the offset into the stack. Then we load it right back again. This is the kind of thing that a peephole optimizer would deal with easily. We don't have one yet but in fact you probably never will but I'm not going to worry about that for now. I'm just going to generate bad code. And then we write it to the variable and the variable lives in subroutine's 1 workspace. That's in fact our workspace is here containing one word. So this does the right and then we exit the subroutine with these three instructions. So what we need to do now is actually emit the stack. So this is going to be maxsp-1. That's allocated one slot of RAM for the virtual stack. And now we want to do the constant pool. Now the constant pool is going to be a little bit exciting because we're going to have to store them. The constant pool has to live in fixed memory. We're going to accumulate constants as we compile the program and then emit them all in a chunk at the end. And we're just going to do a link list of constants. So ID will be this number here which will allow us to refer to the constant. We then need to store the value but we're going to not just store numeric constants but symbolic ones as well which are referred to by a symbol and offset. So that's going to have to be struct symbol sim, 32c offset. And let's have a function that adds a constant and we also need a link list of constants. So all we're going to do is walk through the list. If a constant matches, then just return that ID. So we have found no matching constant. So let's create a new one, initialize it, allocate a new ID for it, add it to the beginning of our linked list and return the ID. There we go. So in our push constant, we do add constant, sum off. There you go. Does that work? That works. And it's actually managed to allocate ID 2 because one is used by our subroutine. We're just going to use the same ID field wherever. Right, there's also a bit we're missing which is in our epilogue we actually need to dump all the constants essentially just boom, we want to emit a label and now we want to actually emit the constant itself. Now we're going to, from now I'm just going to assume these are all numeric constants which means that the symbol will not be set because this allows us to just simplify things for now. Ah, this is not data, this is code because it needs to go into the fixed memory. Okay, C2 decimal 1. And remember that this is actually both of the files concatenated one after the other although in a really weird way. I don't want to do it like this and I think I also want N, no. I thought there was a option which printed the file name between files but apparently I was mistaken. So let's actually run it and see what happens. Assemble, okay. Uh oh. Now you see, I thought I tried this. Ah. See, I thought I'd check to make sure that you all understood these operators. I think it does but you need white space. Yeah, good. I would have been quite stuffed without that actually because I didn't need to generate IDs for everything I wanted to refer to. This code generator likes generating code in big blocks. It likes generating the data tables in big chunks and then using offsets to refer to them. Okay, so that assembles. Let's see, oh good, that works. I'm going to break it F1, run it. Okay, load constant into A. It's a 1. Store into S1. That's a stack sort. Load from S1. Store into W1 and exit. All right, that seems like a decent start. In fact, that's actually quite a lot of a start. Now you should get constant folding by default due to the compiler course you do it. Yup, deck two. Let's just check that the constant pool works by defining two labels. Yup, we only have a single pool here. Our subroutine workspace has expanded to contain an additional field. Okay. Next thing, let's try loading a variable. So we store one into I and we can use Cal goal, simple type inference. Just do that. Yes, arch push value has failed. Push value. Right, what this does is it loads whatever's in the symbol specified and pushes it onto the stack. So this is going to be CA. Clear and add. This is our load from memory. And this is actually a... We're loading from RAM. So I'm going to use CAE. CAE and CAF all produce the same fundamental opcode but Yule gives a bit of extra error checking which is always nice. And then we store it onto the... No, sorry, that's wrong. We're actually reading a variable. So we want to read it out of a workspace. So this is going to be the ID of the subroutine whose workspace the symbol belongs to is going to be that. Now we want to store it onto the stack. So I believe that's all right. So what's that done? So I'm looking at... Here we go. I should have put in a feature where it displays source line numbers in the resulting code but I kind of didn't. So load out of workspace one. Store it onto the stack. Load it off the stack. Store it into that offset is wrong. That needs to be different. Yeah, what I've done wrong is... I need to do this offset. So the symbol itself is encoded as the owner subroutine and the offset into the subroutine's workspace but then there is another offset which is then applied on top of that which we need to add on. So that should be used as... Still hasn't worked. Zero. Oh, right. I need to apply this same fix to the sign var. Yeah, u.var.offset plus offset. There you go. So this is now storing into offset one in subroutine one's workspace. Good. So we can now define variables. Let's try adding... Let's do some arithmetic and arch add fails. Add this one. What this does is it adds two items on the stack and pushes the result. A more sophisticated code generator or at least if I cared about supporting more than one type would need to pay attention to the type field throughout. But all we need to do for this is... We need to add two things and put the result back into memory. So this... Well, the destination is... What we're going to have on the stack is x, y and then we're going to have as a result x plus y. So the overall result is to pop y and then add y to the value of x in place. It turns out that the AGC has some opcodes to help with this. So we've got the traditional add instruction. Add, read the memory location and add the value at that memory location to the accumulator. This is traditional enough. We also have ADS. And ADS does exactly the same thing but it writes the result back to memory. So we need to pop the right-hand side which we're just going to do by loading it from the stack and then we add on the left-hand side and we don't change the stack pointer. Now, I actually think I need a stack pointer minus one for that because we are a pre-increment stack. So what code is this produced? Oh, yeah. And I need my operators. So we have... We load S1 plus 1. We add on S1 plus 0 and write back. We read back S1 plus 0, store it into W1 plus 2. That looks alright. Let us run that and see what happens. Wow, it assembled first time. Code at AGC. Okay. So we're writing 1 to S1 plus 0. Unfortunately, this debugger doesn't support display and there are some GUI debuggers that work with this but the only one I found that I could actually make work is code blocks and the problem with code blocks is it requires you to set up a project with a build system and everything like that, which I just don't have. So I have to use the command line debugger. It's okay once you get used to it, I suppose. So this is storing 2, right out of our constant pool, into our other variable and I actually need to go through and... That puts it onto the stack. It reads it off the stack. That writes it to the workspace. This is reading a workspace variable and stacking it. Reading a workspace variable and stacking it. Can I do... No, that hasn't worked. I mean the debugging hasn't worked. So this should load S1 plus 1 from memory, giving me 2. Then add to S1 plus 0 and it'll leave the result in A as well as writing it back. So we get a 3. That's nice, that's correct. We load it from memory because the code generator doesn't know it's been left in A. People optimize it again. Not going to worry about it. Okay, we have loaded 3 from memory. That's worked. We've managed to add 2 values. We've stored it into our workspace. Yeah, let me exit. Right, we can add 2 numbers. What we can't do is add on a constant because that goes through a different workflow. But frankly it's easy. So what this does is it adds the supplied constant value to the thing on the top of the stack. So this is exactly the same code we're going to use except that instead of this being a stack reference it's going to be a constant pool reference to our... All right. So here is our add and C2 here contains our 1 which we've used previously. Constant pool is working just fine. We add and store it. Yeah, okay. That looks fine. Now, the thing I want to do just for ease of debugging is I'm going to add in some comments. I don't need one there. Add subroutine prolog. I am going to add a comment here. This will just prefix each subroutine with the name of the subroutine. I am also going to add comments here with the name of the symbol and I'm going to do that wherever we use a w. So this is push value. I also need a sine var here. Oh yeah. And this also wants to be a CAE because I am reading out of fixed memory. This should make the resulting code assuming it works easier to understand because now it tells us that this is referring to a variable i. Let's look at a few more spaces in there. Yep. I'm struggling to work my Vim. That's better. What's happened to CAE? That is a push var push value. Yeah. Okay, much improved. That's actually a decent milestone. Err, arithmetic works. Okay. Now, where we're going with this is we want to write a game. So one of the things we're going to want to do is we're going to display stuff on this, the disk key. The AGC has a whole pile of IO channels which is basically IOPorts if used for modern machine and the disk key is attached to an IOPort. I think I've got the documentation for it somewhere because I knew I would need it. It's this one. What you do is you write formatted words to a particular port and it changes the state of the disk key. It essentially it's got a few bytes of video RAM. Yeah, this is a very fuzzy word, wording. Each quote byte, unquote, represents two digits and the instructions you give it consist of the address of the thing in video RAM you want to update followed by encoded opcodes to tell it what you want the state to be. So we are going to want to do this because our game is going to be no use whatsoever without being able to write to the disk key. And to do this, we're going to need subroutines which we're going to want. Let's just start small with... So this is going to be the digit pair is going to identify which of the pairs we're going to update. This lookup table here tells you which ones. So, you know, if we want to update digit pair 11 then this changes M1 and M2, which are the two prog things. And we're just going to have... Yeah, and there's actually a bug in my... Actually, I think I can fix that. There's currently a bug in the parser. Yeah, you have to have one statement in a sub. I think that would do it. Does that kind of work? No. Okay, I'll deal with that later. For now, I'm just going to put in a semicolon here just to keep the parser happy. Okay, now, if we try to... Why did that work at all? I'm slightly confused by the fact that it actually worked. I was expecting that to fail miserably. Okay, I know what's happened. Yeah, I think I did do a bad merge because it's missing a core piece. Yes, it is. Right. So, CalGol supports nested subroutines. I've nested this subroutine inside the main program, and if I want, I could put more inside that. Doing this properly, each of the subroutines should be taken out of line and omitted one at a time, but we're not doing it properly, we're bodging it. So, we're just emitting all our code from top to bottom. This means that whenever it sees a nested subroutine, it's in the middle of generating the code for the outer subroutine. So, we just have to jump over it. And I appear to have lost the jump label. So, let's just do... I remember what I did. So, this is easy. Before we call the nested subroutine prolog, we emit a jump to the... after the epilogue. And after the epilogue, we actually emit the label, like so. Right. And I also need to put in a bit more... just put in a blank line there to make it usable. Okay. So, this is our main subroutine, which extends from here to here. Here is our nested subroutine. So, we enter the main subroutine. We then do a TCF, which jumps over it to here. And we exit. Meanwhile, the nested subroutine has got its prolog, its epilogue, and... yep, and down here, it has emitted the workspace needed. I'm a little disturbed by this stack stuff. This is because there is no stack usage. So, let us just... the current sub... it's not equal to zero. Just do that, that's better. We also do that from the workspace. Okay. So, subroutine 2, which is diskey set opcode, has got three words of workspace for our three parameters. They've been allocated by the compiler core. And every subroutine has one word to store the queue register in. Now, the reason why I was expecting this not to work is we also need to add some code to load the parameters. And this is where it's going to get rather interesting. So, I have to pass parameters on the stack, but I have no stack. So, how am I going to pass parameters? This is actually the second version of the Calcol compiler. The first version was vastly more complex, completely self-hosted, would run on like a Z80. I managed to recompile part of the Calcol compiler with itself on a 2MHz 6502. It took forever. The way it did things is that when you called a subroutine, it would poke the parameters directly into the subroutine workspace. This worked fine, but if you have situations where you have this kind of expression, so the compiler comes along, it reads this, it says, oh, you're calling foo. Here's a parameter. Poke that into the first word of workspace. It's made out of three for clarity. Poke that into the first parameter of foo's workspace. Evaluate this. Oh, you're calling foo. Poke one into the first word of foo's workspace. Poke two, call foo, get the result. Poke that into the second word of foo's workspace, but this call here has corrupted this. And in fact, it got worse. I'm amazed my compiler worked at all because other calls to foo happening from elsewhere, if I actually did that, but bar was calling foo. Now, the language prohibits recursion, but you're allowed to do that because when bar is calling foo, foo hasn't run, but we're still relying on foo's state. So that's just not going to work. So what we have to do is when we call a subroutine, we have to pass a pointer to where the parameters are stored in the callers stack, which we will then, in the call E, dereference the variable, load the pointers, poke them into the call E's workspace, et cetera, et cetera, et cetera. So rather than deal with the call E stage, I'm actually just going to go straight for the caller. Let's just do set up code 1, 2, 3. And now we run that. It fails due to arch push input param. And this is what this does is it tells the back end that thing on the top of the stack is an input parameter. The 8080 code generator, the thing on the top of the stack might not actually have been real. It might have been a constant, waiting to be rematerialized. But now code generator is always real, so that is empty. Now it's failed with arch emit call. So I'm just going to take that out so that this will run. And you can see here, this is where we've started doing the call. We are assembling the parameters into the stack from left to right. We're reading a 1 poking it to the virtual stack. We're reading a 2 poking the virtual stack, et cetera. And our actual call will go here above the extend. So what we need to do here is get a pointer to our current stack, which is going to be A. Going to put this into A because it has to go into A. It's a constant. And this is going to be... This is actually our first non-numeric constant. And I think I screwed it up. What we're going to need to end up with is CFC99, C99. And this is going to have to be a reference. We use the symbol word for this. This will... Oh, there is symbol. Interesting. See, I saw this in another piece of source code. I hope I wasn't hallucinating into this version of... This version of UL actually supports it. So what this possibly mythical symbol pseudo-operation... What it does is it allows you to insert the address of a symbol into the opcode stream. Now, how are we going to do this? We're going to bodge it. So allocate an ID, stack reference, eData. So this is going to be a symbol referring to... We want the current subroutines virtual stack plus the stack pointer minus one. No, actually, because our stack grows up. So we in fact are going to need parameters. This will actually give us a pointer to the base of the parameter block. Here we are going to simply load the address into the A register. And then we transfer control to the callee. How does that actually work? Here is our symbol lines. So let's push it through our UL. Unrecognized opcode pseudo op symbol blast. I hope I can do this. So we've got... What have we got in terms of pseudo codes? We've got two FCE address. Emits a double word constant. This is for doing far calls. The AGC RAM... Well, the AGC ROM is banked. And the DTCF instruction here allows you to switch banks and jump to a particular address. Is there a 1f adder? Raise memory op6 is set lock. I wonder if just DEC will work, to be honest. For a decimal constant. Oh, I think that worked. Sorry D. Line 20 address is not in fixed memory. That's not data. That's a constant. I'm going to have to use the constant pool for this. Yeah. What happened is, is actually put it into the RAM section. And luckily our CAF here, which only allows fixed memory. This ROM has caught it for us. Okay. I'm going to have to do this slightly properly. I think we're going to need to have different types of constant. But I really don't want to do that. That's going to be a bind. I could, you know, just store the strings. Yeah, let's just store the strings. It's a bodge. So size of buffer s app. End. Okay. All right. So if the string matches otherwise, there we go. That's terrible. So this is just going to be, and we're actually going to be using the symbolic constant quite a lot. So we'd actually make a helper for those sim. If it's numeric constant, then just store the offset part. Else it's a reference to a variable always. So again, this is going to be our long complicated expression, which is the, the owners ID, sub ID plus the offset plus the extra offset. Okay. And now we go to our epilogue. And this is very simple. It's just, okay. Just that build. Support those now. So that means that this is now just a perfectly ordinary constant. And let's hope this works. So what's that done? Here is where we are loading the workspace. We're loading the pointer to the parameters. And it's loading this constant, which is s one plus zero, which is the beginning of the subroutine's virtual stack, which is the first parameter. Good. That works. Now let's see if it assembles. That's nice. Run it and let's see if the things actually set correctly. Break at x one run. We are code. We're here. C3. This is just story stuff. Okay. C6. What is the A set to zero? That wasn't what I wanted. Well, that looks all right. These constants are all zero. That's surprising. Format is a repeat count. So let's actually look at what's in s one garbage. I think I haven't read it correctly yet. Address is garbage. Oh, that's showing. That's dereferencing. I want to do that. That's better. So, okay. So it has in fact loaded those three values correctly. We are at, why is Z being reported as zero? Z's not zero. I think the debug has gone mad. Defaults for format and size letters are those previously used. Default count is one. Let's do that again. Okay. Break x one. Actually, no, let's break in F2. Okay. We're at x one. We're at F2. What's a zero? That's not right. Okay. I think what it's done is it's tried to pass this as a decimal constant and has failed. I can figure this out for sure by simply looking at the assemble binary. So we're looking for, so, yeah, 15 bit words, but they're actually packed into, they're packed into the binary as 16 bit words, but each one is shifted. Each 15 bit word is shifted left. So the bottom bit is zero. So just to make things extra unreadable. So we've got a 060402. That's our 123. Right. And here in fact is our constant. That just hasn't worked. Okay. I am going to go figure this out and restart. Be back in a moment. Okay. I think this is both much easier and much worse than I was expecting. So I think all we need to do here is to simply do TC and, where's my hex editor gone? There. So here we see our 642 and before it, here is the address. I've shifted left by one. How does that work? Turns out the TC instruction, which takes a address as its parameter, is encoded as a 0 plus the address. So what this has done is it's actually just admitted the address into the code. That's vile, but it might just work. So let's actually try this for real F2 run. Right. What is A set to? A is set to 4A. What is the address of S1? 4A. Right. Phew. That's worked. I was worried there for a moment. So that is part of the call. The other part of the call is one of the many parts of the call is the Cal goal calling convention requires the called subroutine to adjust the stack to consume the parameters. So we actually are going to need to retract over. In fact, we can just do that here because that simplifies the code like this. The back end, the front-end compiler does not currently support output parameters. I'm hoping not to need them. Okay. So now we can call subroutines, but our subroutines can't be called. So let's fix that. Right. In the subroutine prologue, we then need to index through the parameters. And we need to work through the parameters capacity and dereferencing them and place them in the subroutines workspace. Now, we can save a lot of time because we know that all our parameters the same size. So we don't need to bother about like the, we don't need to bother about walking the parameter list. We can just use the count. As I said, this is a bodge. So if current subroutine has some input parameters, then we know a will contain the address. We wish to put this somewhere safe. We're going to use Q, which we just corrupted for this. So this is going to be, we are going to use TS transfer storage because we know that a is an address. And therefore cannot have over flown. So store that in Q. And now one parameter at a time if you want to load it. Okay. Now the AGC does have support for dereferencing pointers, but I don't think the designers were expecting it to be used for that. This is the index instruction. The index instruction is mainly intended for jump tables. What it does is it evaluates its parameter, which is a memory location. So the contents of the memory location. And then the next instruction gets that value added to its parameter. So this takes the value in a as it to jump tab and therefore produces a jump table. Now we can use this by reading the value in Q, then loading a parameter. Then we can use TS because we know that has been loaded from memory, storing it into our workspace. Okay. So this has generated this code has compiled that good. I was actually a little worried that you would reject it. I do not believe that the Apollo programmers use pointers anywhere. Pointers are a relatively modern invention. Well, everything computer wise is relative given that the initial version of this stuff is written before 1960. Yeah, you literally have to be over 80 to worked with any of this in real life. That's terrifying. Okay. Here we are in our code subroutine. Why is a zero is a actually zero a is zero. Why is a zero? Yeah, what I did was I, I modified my test program but didn't actually change my real program. Okay, let's try that. Yeah. TCS one plus zero. You will a GC break to run. Yeah. What is a is a pointer. So save Q to transfer the value of a to Q. What is Q Q is a thing index load. What is a is a one that is the right value. Okay. I believe that has possibly done the right thing. Let's look at our workspace 123 that has correctly copied our parameters into the workspace. We can call functions. Fantastic. Okay, let's do a bit of code now like real code. So the disk key documentation. So the, the digit reference is stored in the, in this field of bits that is 1234567891011 to the left. So the op code we want to send to the disk key is going to be digit pair times two to the 11. I do not actually have shifts yet in the back end language. And besides the AGC shifts are unspeakable. So to I've got my calculator out here. Two to the 11 is of course 2048. And then we then going to add the left op code, which is see the left hand digit. That is 12345 and just in case of embarrassments two to the five is of course 32. I was just checking left times 32 plus right. Okay. And we now want to write that to the disk key itself. And we're going to use inline assembly for that. But in fact, we're going to do that in a moment because we wish to, we have to do some more compilation work. Okay, we cannot multiply by constants. Let us multiply by constants. Now, I wouldn't even attempt to do this if I hadn't already checked. And the AGC does have a multiply instruction. It actually multiplies a double word value. That is a 30 bit integer. Sorry, let me do that again. It multiplies to 15 bit integers to give a 30 bit integer. Now, this is all kinds of weird. You haven't seen divide yet. Divide is something else. So what does this do? Okay, the contents of the memory location are multiplied by the accumulator. And the result goes into a L. Okay, that is fine for us. So all we, let me just double check the 12 bit memory address means that it can access the entire memory map. Some instructions take a 10 bit memory address, which means they can only address RAM. So we would have to copy the value into RAM to make that work. So extend, multiply by, and this is a constant. So add constant null comma value. Simple enough. Oh yes, we do want to, of course, pop the value into the accumulator. So that I could actually just factor that out. I'll just cut and paste it for now. So that bang was me dropping my mouse. And now we are going to, this is an arithmetic operation. So it may have overflow. Let me see. The overflow is cleared. There is no overflow for multiply. That's surprising. I am extremely surprised and somewhat suspicious. So let's actually use the exchange instruction just to be on the safe side, because the exchange instruction drops overflow. Okay, what's that done? Well, that did something. It's generated quite a lot of code. C3 is blank. Oh, oops. That's better. C3 is 2048. So load our parameters of the parameter block. Load our first parameter, which is digit pair S2. Load the, oh yeah, load it back again. Then we do our multiplication against the constant C3. Store it back into S. You store it back into the same stack slot. Then we, for the left hand, for left here, we do the same multiplication. Store it back into S2 plus one. Then we add the two together. Then we read, write, stack it, add it. Okay, that looks suspiciously promising. Now we're going to have to actually write to the disk key. So the first thing is we want to get our value into a, next we want to do a extend, and next we want to write to the IOPORT. And this is output channel 10 octals. So 10. I think that will do it. This one I assemble it is going to bail because we haven't done our inline assembly yet. So we are actually just going to crudely bodge this. Print some spaces, print a string, print a symbol, stdera, what am I thinking of? So this means if there's a label in front of an asm, it ain't going to work. Hopefully we should get a nice error such as AGC crashing again. And asm end is just a new line. Okay, build that, and it's failed. That's, we don't pass in the extra offset for this. We should, no we shouldn't actually. This actually needs to be a variable reference. The reason for passing in the extra offset is so that you can do things like refer directly to array members, but you're not allowed to do that inline assembly. It has to be an actual variable. So we're going to call that VAR. Okay, and here is our, and I'd forgotten that we actually want to, we need to put spaces each side of each value. So we load the value, write it to the IO port. All right, moment of truth time. Let's actually see if I can write a value. Here is the map of what values you can write. So let's put in a, we got here. Nothing very interesting. Let's put one of these in. That's a 13, 13 octal. Yeah. Do we have the other one? No, we don't. Let's just do a 13 and a 13 and a 14 octal. I so, so rarely use octal, like never. So here are our 13 and our 14. Okay, assemble. It assembles. Load up the simulator. Fire the disk key, hit the button a few times just to make it notice the simulator's there and run it. Hmm, that was not really what I was looking for. I've had a certain amount of trouble making the disk key work, to be honest. It's kind of unhappy about something my programs do, but I am not sure what. I don't think I have to enable it somehow. Some of the other hardware you do have to enable, but so this should be digit 34 and 35, which is the bottom ones. And I have poked some of these values and just like had them work. So here's a write to 10. Yeah, my test program did work. Write channel KC. You can accumulate it into, yeah, yeah. KC is an IO channel. Nine bit, extra code. Your flow is set to the result of the operation. Yeah, A, L and Q are actually also IO ports so that you can use write to write to one and read to read from one. This is actually useful in some really bizarre situations. Intriguing. Okay, let's actually walk through this. Okay, so we are loading our values. Ah, S does work, thank goodness. A lot of the abbreviations don't in this. Right, here we are actually doing code. Right, we are about to do the multiplication. We are multiplying one by this number, which is 2048. So step, what is A? Zero. What is L? What is A? Okay, it's put the result in L. Why? Okay, that's the wrong one. MSU is a great instruction. Remember how I said earlier that it uses one's complement everywhere except when it uses two's complement? Some of the IO devices return two complement values and this instruction will convert from one to the other. They're just wedged in instructions anywhere when they would be useful. The entire instruction set is a random mess. It worked, but the least significant word is in the L register. Okay, so A contains all the overflow data. That's why it doesn't set the overflow bit. Because if you multiply the two with its equivalent of a max int, then you still won't overflow a 32-bit result. So where's our mole? Is there a... I was not aware of this instruction. It writes a zero to L. That's useful. Oh. Register zero is hard-wired to zero. That actually is useful. I'm glad I saw that. If ever we want to zero, then we don't need to bother about a constant. Huh. So... Is there an L... Yeah. L changes the value of L with the value in erasable memory. It's not an extra code. Overflow is not affected in this case. The accumulator, fantastic. This will just store L. We don't need to worry about all the overflow stuff for that. Now let's try it. Dabadiski. Run. Okay. Oh, actually, let me just hammer this a bit. Then run it. And I'm also not sure why I'm continually hitting F2, to be honest. It shouldn't. It should hit it once. Right, there's something actually very wrong there. So code. F2. So we do our multiplication. Say XL. XL is 800. S2. Right, we stored that in there as we expect. We now do the left-hand side. Okay. So we have encoded the left-hand digit. We've encoded the right-hand digit. Now we get on to our inline assembly. A is our opcode. Extend right to 10. Right, that should have done something. Extend. Q exchange, Q2. Q is a address. Return. We should have returned to here. Extend. Q exchange, Q1. Return. Injuring. Is this actually still running? Yep, it's still running. It hasn't crashed. It thinks it's connected. Well, it does not now. It does think it's connected. Okay. So let me double-check my program. See, I actually have a test program here that somebody wrote which actually like stores it. It does work. I can run this. Oh yeah, it does work. So I know that it is actually possible to write to this thing. So the problem is my code somewhere. This is the test program. It's all AGC machine code. So these represent various digits. This syntax here means one shifted left by 11 bits, which I believe is 2048. I'm going to just do a... So the documentation here does actually have an example. So this should display... This particular sequence should display 2, 3 in register 1. That is the top number. So let's just actually try that and see that it works. Make your all AGC run... Oh, I'm running the referral program. Not my test program. Test AGC.bin. Run. Okay. So I did have to poke it a bit, but it did actually come up with a number. So I think that has actually worked. So why didn't it work with my number? So the disk key here is a separate program that talks to the simulator via a socket. And it does reconnect when you restart the simulator, but not always immediately, which is not terribly satisfactory. Okay, I believe I am miscalculating my bit field. So let us actually siphon out the... the actual value that we're writing and then we turn it into binary and take it apart and see that it actually works. 96C. Now I need to turn that into binary for which I will use my... Okay, it's actually... 116 is 010C is 1100, I believe. Excuse me. So here is the value that they suggested. So the... we've got... let me take those spaces out. It's five bits per... yep, per digit, followed by one bit for the... for the... things like setting the plus and minus, followed by four bits for the actual opcode. So this is the thing I tried to send and this is the thing it tried to receive. Well, let us change our codes that are actually generating what they are sending, because maybe this disk key doesn't understand non-digit numbers. It should, it's... but yeah, let's just try and see. So this is... 9 plus 30... I don't trust my ability to do mental arithmetic, so I'd much rather use tools for this. 11001... in decimal is 25. 11011... 11011 in decimal is 27. And I'm going to swap those round so that I will actually be able to see something change on the disk key. So run your... run AGC, poke the disk key a few times, run. That changed. Okay, I just don't think it likes non-digit numbers. But I believe we are getting somewhere now. So we now... actually I'm just going to put in the... I do not believe the sign bit is used for... this is digit 7 since this one. 1213... Interesting, where is the... Oh, yeah. So it doesn't have... there are actually two sign bits. So you get a positive sign bit and a negative sign bit. And if you set... if you unset them both, then you don't get any sign at all, which is what the situation is here. So to test that, let's actually just set that to 1. Again, let's just swap these round again. Make... your AGC... run... poke. There we go. So that has actually worked. Good. I was extremely worried that wasn't going to work. So we can now set... raw ROP codes. We want to actually... now write a value. I'm wondering the best way to do this. The mapping between... digits and values is kind of strange. And I do not believe that it's possible to... convert from one to the other. I think you need a lookup table. See, what I'm wondering is, do I actually need to go and support... subroutine return codes for this? I think to do it properly, yes. But... in the interest of simplicity, we're just going to budget. So the... I'm looking for a lookup table. Here is the lookup table. So what we're just going to do is... steal that... and embed it into our test program. It's a complete violation of everything we hold dear. It's going to actually let us get stuff done. So the way to do this properly is that I go away and... add support for... return values to the subroutines, which just haven't done yet, haven't got around to it yet. And then I would write a subroutine that does this conversion. But we're not going to do that. We're just going to do this key digit. And this is going to... it's going to receive the digit we want in A. And we will then load a value from the lookup table and return. That's all it does. So that's actually like commendably dense for this ancient architecture. So what this does is... what this does is... we're going to call it with a value in A and we don't need to use the traditional... the cowgirl calling convention for this. It's a help up tool. The digit is going to be in A. It adds that onto the address of digit paths here. Loads it. And we convert 0 to 9 to one of these. Okay, back to our test program. So this is going to contain a digit pair. A... sign. Left and the right. So just like the one above. But this is going to... use raw digits. So we... we load left. We call this the digit. That leaves the results in A. We know it can't overflow because it's just loaded it from a constant. And we store it back into left. We do the same for right. Disci set raw op code. Digit pair. Sign. Left. Right. There we go. Make. Symbol diskey set. Okay. A, G, C. Poke this key a few times. Run. Four and a three. Fantastic. Exactly what we wanted. Okay, now we're going to start with actually setting complete values. So register is going to... because we actually have... Traditionally, this line is register one. Pointing this. This is register one. This is register two. This is register three. And then you've got prog-verb and noun, which are separate. These are two digit values. We're really going to want to set all of these to do this right. Can I do this all in a single... single operation? I think we probably can. So register is going to be one, two, three, four, five, six. And there's also going to be a value. We are going to want to disassemble our number into digits. And we need a array for this. We do have arrays. A, we have five digits. So we do that. Now we need to actually decompose our value into individual digits. I'm wondering if this is the right way to do it. I don't think it is. I don't think we want an array. Yeah, we're actually going to work... We're going to work right to left. So we're going to start needing to do conditionals. If register is one... Let me find this. If register is one, we start at 15. 25, 15, 25, 35. Octal. Octal is always octal. Gach, fourth and digit there. I haven't implemented else or else yet. M1. Now hang on though, these aren't the addresses. These are the... Gach. There's two sets of lookup tables. 14, 15 is six. 24, 25 is... Wait a minute. 24, 25. 11, 12, 13, 15, 15. 21, 22, 23, 24. 25. Split with 31. That makes no sense. 11. Oh dear. Okay, that's really awkward. See, I was expecting to be able to simply point at the digit pair on the right. And then we start decomposing the number into digits while working left. But we can't. I think we're actually going to need to decompose it into digits into an array. And then we probably need custom code for each of the registers to set the values. Oh well. Okay, let's have an array. Let's have an array. Five digits while i is not equal to zero. Okay, I think that's enough for now. What we're going to do is divide value and calculate the remainder. Now it's not going to work because we don't have equals const. So let us start with conditionals. Right, conditionals are where the AGC really starts showing its unique ways of doing things. So the way this particular core works is it says if this symbol is equal to this constant value, jump to true label, otherwise jump to false label. And we are just going to check if value is zero. Sorry, if the value on the stack is equal to sim and value. This is, that should be off actually. If this, because you're allowed to compare pointers. So if sim is set, then sim and off represent a label. If sim is not set, then off is a value. So if you say, if not sim and value equals zero. So this means is it actually the constant zero? Then we can use optimized routines. Otherwise, we will not implement that for now. Now actually we're going to, yeah, we're not going to implement that for now. So comparing again zero. BZF branch zero to fixed. Jumps to a memory location in fixed memory if the accumulator is zero. So the first thing we are going to need to do is load the value from the stack. Plus the stack offset. And we are popping this because we are consuming it. So it branches if the accumulator is zero. And this is, by the way, sign. This is one's complement. A one's complement value. Actually start with a two's complement value. A two's complement value has got one is zero is, that's binary on the right. Minus one is, so the advantage is that the difference between zero and minus one is the same as the difference between one and zero. Now one's complement does this. One is zero is minus one is, and in fact it's worse than that because this is plus zero. And minus zero is also a thing. So yes, the documentation refers to behaviors if it's plus zero or minus zero. And also on the AGC if you take plus one and you add minus one, the result is minus zero. This is apparently documented. Okay, that should be straightforward. Load the value of the stack. If it is zero, drum to true label. If it is false, well otherwise, jump to false label. And our hypothetical and mostly mythical peephole optimizer will then take care of making sure that that will take care of making sure that the code looks reasonably good. Two labels emitted in a row. Okay, what am I going to do about that? Is there a pseudo operation that just doesn't increment the program counter? Bank some. Yoyon silently discards this directive. Let's try, let's just try sub row, shall I? Okay, now that is the height of evilness and I do not know if it will work at all. Here is our program. It's beginning to get quite big now. It's going to get bigger. But my, what do you mean two labels emitted in a row? There's no label there. That's a perfectly legitimate label. And then this one, this then sets the new label. Okay, that appears to be discarding a label. I don't really want to step through this. That's a step through the debugger. See if I can do this the clever way. So has that done anything resembling the right thing? Where are we? Label namespace and IDs don't are different. Okay. Well, as this happens immediately after our branch BZF. Okay. After here, complete as constant. That calls const. Actually, before I do that, let's just put back our error. Okay. So we're here. Now we can better break point on the label label. Label is blank. Okay. Label contains six. Where did this happen? 187. We are right. This. Yeah. This is when you call arch label alias, this is always going to happen. So we are going to budget. So we have a label. No instruction. Right. And that also explains why we weren't seeing anything because the other half of this particular operation was being emitted into the data segment rather than the code segment. So yeah, I think if we could do if label zero encode sub row. Right. So what's happened here is this is our while loop here. It's compiled this expression and is generated to new labels for the true and the false destinations. However, the while loop proper has generated the label for the beginning and a label for the end. The beginning is has to be there so we can jump back to the beginning and the end has to be there in order to, you know, terminate the loop. So the false label generated by this needs to be connected up to the exit label generated by the while loop. So what we do is we just equate them together. So you're saying here we can see if the condition if if the condition fails, then we jump to X7. That is the conditions false label X7 is equal to X5. X5 has been generated by while and is then emitted here. This is the end of loop label. So X6 here is the true label. So let us actually just try assembling that. And I'm not going to pipe the result to null because I want to look at the symbol table. So I want to see if X6 and X7 are similar. Oops, fake letters too. And required extend is missing. That's easy enough. BZF121 addresses not in in. Yep, that's simple enough. That's constant. That needs to be fixed memory. Fatal error zero. Now X5 and X6. So this actually gets sorted them. X5 4254 X6 4245. Oh no, it's X7 I wanted. Yeah, these are in fact the same label. Good. I believe that has even worked. Right. So we know we have loops by the way and conditionals if will work. So we have looped. So we now want to divide value by 10 and get the remainder. This is going to take, this is going to bring us to the AGC's mind boggling div opcode. At least it's got hardware divide. The trouble with div is it's kind of weird. It produces the division and the remainder, which is fairly normal for a div, to be honest. And we want both division and the remainder, but we can't get it using our API. So what we're actually going to do is just go. And in fact, it's much nicer to do a multiply. So we then do remainder equals value minus 0 times 10. And the remainder, the remainder actually goes straight into an array like so. And then we can say value equals new value. Okay, set register 1 comma 1, 2, 3, 4, 5, 4. So what's this going to complain about? Div constant. We can't divide by a constant. Now, the thing about div is it's quite expensive. It takes a whole 70 microseconds. It takes a 30-bit pair, divides it by a single precision. This is a 15-bit value to produce the result. The problem is the pair that you're dividing by must be bigger than the thing you're dividing it by. Otherwise it reduces garbage. Now, because we're producing a register, we're dividing a register pair in A and L. But with the thing we're dividing it by is a single precision value. This is a 15-bit. This documentation is all based on double precision numbers and single precision numbers. They all represent numbers between minus 1 and plus 1 using fixed point representation. But we're just dealing with integers. The integer is required to be larger than the one's complement integer in A, leaving the quotient in A and the remainder in L. This is natural since the quotient would be too large to fit in the A register. If you're dividing 2 by 3, we obviously get 0. So why would the quotient be too large? To generate code for this, we are dividing by a constant. The thing we're dividing by, which is the k, is going to be in a constant pool. So we know that we are at some point going to need to do dv and constant, dv value. The thing we're actually dividing needs to be in A. And I bet this is an extra code, so it is an extra code. But the requirement that we are... So we are also going to have to 0L, which now that I found that ZL instruction is easy. But before we do any of this, we have to compare the two liney. We need to compare the two things we're dividing to check for the larger, the largeness. And we need to do this without mangling signs. It does say that it's the magnitude that's the issue. Is there a way to get the absolute value here? So this is not part of the AGC instruction set. This is part of the interpreter, which is a big chunk of AGC code, which was used for doing stuff like trigonometry. How do we take the absolute value? Well, the simplest thing to do, to be honest, that's probably an easier way to do this, is to mask off... This is an AND instruction. Okay, so what we're going to need to do is mask it with... Of course, a constant, which is a 14-bit value. So this is the mask, including the sine bit, but we want to drop the sine bit, so this becomes a 3. And we then put it in L. And we need to subtract... Have we subtracted yet? We haven't subtracted yet. In fact, we are subtracting a constant, so we could just like... So we could just add the negative. Okay, so this particular ANDing is two's complement, because it's done on the compiler. So we're just going to mask off the top of the value, including all the sine stuff. If it's negative, that ain't going to work. We actually want the absolute value. The properly absolute value. Yeah, there is actually. Okay, now we need to compare. This is going to take us to the other comparison. This is why I did the Optimize, compare with zero, partly because it uses better code, but partly because we don't have to touch this. Where is it? Count, compare, and skip. What this does is, it stores a variable from RAM in the accumulator, decrements it, and then performs one of several jumps based on the original value of the variable before it was decremented. Now, our value is actually in the accumulator, so we are just going to do CSA, because remember that the accumulator is memory mapped. That actually, A is at address zero, so. Now then, once it's actually done this, it then jumps to one of the four instructions following, depending on whether it is greater than zero, greater than zero, or positive overflow, equal to zero, equal to positive zero, less than zero or negative overflow, or equal to negative zero. What we want to do is we are subtracting the thing we're dividing from the thing we're dividing by. So we've subtracted the thing we're dividing by, therefore if the value is zero or negative, having the two values the same is also considered a no-no with the divide instruction. Having the value zero or negative means that, twice equal and they are non-zero, then the A register we stored with garbage. So only in the case where the result is strictly positive do you want to proceed to this. Otherwise, we want to load the result with zero. How do we do this? Well, we want to create a couple of labels. So this is simply going to be... So they said that register seven is hard-wired to zero. No name, not to say it's called memory, hard-wired to zero. So let me just go to my test framework and add a new register name. So we can just say zero, yikes. Okay. At this point, we want to actually do the... insert the jumps to the appropriate places. So what we've got, greater than zero, then zero, then negative, then negative zero. So in the first situation, the first one is the only one where we want to actually proceed. For all the others, we just want to go to zero. In fact, we don't need that at all. We can just do three knots. Okay, that's slightly terrifying. Oh, and this will leave the result in A and the remainder in L. Do div label, 27 arch sub. Arch sub. Really? Oh, subtract. Okay, that's easy enough. You're not quite that easy because we need to make sure it's the right way around. This is going to load the right-hand side. This is going to load the left-hand side because we pop the right-hand side, then if so, if we just do this, pop the right-hand side, then the left-hand side. So, and we want to put... There's... there is a SBS. There is no SBS. We may actually have to do this the old-fashioned way. Okay, there is no sub in store. There's just a SU that leaves the result in the accumulator, so that's absolutely fine. So we are going to... So we pop the left... Well, we don't pop anything, but we take the left-hand side, we subtract the right-hand side, adjust the stack pointer, and now we want to put the result back into memory. And because we've done arithmetic, we can't use %s. So we do this. ArchAssign pointer. Ooh, pointer dereferences. Yeah, this is the arrays code we're dealing with. We're trying to write to... We've actually, in our test program here, this is a whole bunch of new functionality. What this has to do is take the address of digits and add on i to it, and then dereference the result. The AGC can actually do this in much more efficient code, but we're going to have to do it this way until I do my rewrite. But anyway, how to assign a pointer... ArchAssign pointer. This takes the... On the stack, there is... There is pushed the destination and the value in that order. So destination... Pop the destination into A. Do an index with the destination address and then do a store. And ts is safe because this has come out of memory and therefore cannot overflow. So the value at this address will be red, added to zero, and the store will happen there. Symbol disk set register. This is actually a proper compiler error. Disk set error. Set register. Okay. Now we have quite a big program. What have we got here? Disk set register. Disk set register. All those here. Read the parameters. Set i. This is our while loop. So we do the comparison. There's a really dubious sub row. This is decrementing i. And we can do better than that. If not sim and of equals minus one encode decker. Finding stuff in this document is surprisingly tricky. Is there a decker? There's an incur. I'm not sure there's a decker. Oh, that's disappointing. Okay, apparently there's no decker, but we can do an incur. This is a memory in play, an in place increment value at address instruction. Let me just double check that it is. Okay, it's a label, blah, blah, blah. Is it signature edgy? Yeah, yeah. Yeah, but there is no anyway. Back to our test code. New value equals value divided by 10. I don't know where these new lines are coming from, but this is the division itself. I know where the new lines are coming from. It's because I... So that's kind of terrible. It'd be nice to be able to move this out into a helper tool, but I'm not sure I can. It's got references to specific constants and addresses and so on. Then we get onto this stuff, which will... Signs to new value, fetches new value, multiplies by 10, presumably, stores the result. Sources result on the stack. Right, this is our... Why has that done an ad? Oh, I know what that's doing. This is... Wait a minute. Sorry. Wait a minute. I don't know what that's doing. Why is that a minus two? S8. That's the stack point of that. That's completely mangled the stack. What have we done here? Div const. We are actually dividing by constant... Yeah, we don't want to pop here. We just want to refer to the previous item on the stack, because the result of the stack will be unchanged. The result of that operation will be an unchanged stack. So, how are we... So here is our multiplication by 10, when we then put the result onto the stack position two. I want to find my... There are two multiplies. Why are there two multiplies? There should not be... I think that's multiplied by one, to be honest. Okay, now, complation goes left to right. So what this has actually done here is taken the i and has applied it to produce a pointer to this and pushed that onto the stack. Then we calculate this value. So this stuff up here is it doing the... Is it calculating the pointer? So we're actually... Wait a minute. Where are we taking the address of that? Well, digits is a symbolic constant. So... Ah, yeah, okay. Digits is... So this is not actually generating a pointer. What it's doing is it's using that symbol plus extra offset thing. So the symbol is a fixed address in memory, referring to this array here. But it will carry around the scaled offset, which is i here. So this is scaling the offset. So c13 is going to be one. Yes. So we can actually, like, improve that. Model constant is one. Notice you don't get a symbol for this. You can't multiply by symbols. Then do nothing. The value is unchanged. So that is stuck on the stack. Now we do the... This will be the new value times 10. Yep. Store it back. Do the pointer assignment. That doing the pointer assignment. Copy value to new value. And finish. Okay. Let's see if this actually assembles. Unrecognized opcode to unicode not. Operand out of range. Oh, oh, oh. I know what's happening. Do you remember I said that? Yeah. Some of the instructions, the operand has to be in RAM. So... Yay, that's just what I wanted. So what we're actually going to have to do is... Oh, dear. We want to put that in L. So... E. Yes, L. And we have to do this first because the CAE will restore into A, which will then corrupt. So if you put it after here, that's an SU. Okay, yeah, yeah. That should work. That's an unraisable memory. That'll be the other... is my mistake. Oh, one, three, two. Required extend. It is soon extend. It is an extra code. They were obviously not intending that people subtracted much. I wonder if I've missed an opcode. One, two, eight. The address is not in a raiseable memory. It's not the CAE. No, that should be in the... Yeah, okay, that's much better. Right. So the other one will be the other SU. No, that's okay because that's actually reading it from the stack, which is a raiseable memory. So one, four, four is DV. Oh, yeah. DV is the same. It's got to be in... It's got to be in a raiseable memory. So we just use the same trick. DVL. 162. Required extend is missing. 162. Yeah, if you're doing this for real, assuming that anyone ever actually needs to really write code for the Apollo guidance computer, then index is not an address. Yes, it is. No, it really is an address. Look, it's right there. Unrecognized opcodes, you do opw0. Yeah, I am slightly fading, so... Right, okay. I... I think it might be this one that's wrong actually, and the line numbers are wrong. Let's try this. No, that's not helped. Okay, this might be a your bug because that is certainly an address. Are we using index anywhere else? That's just... Yeah, okay. Let me take the docs for this because I think it said index was index K and K should be an address. So the thing about... Oh! Oh, that's interesting. Yeah, the thing about Yule is that it was developed to compile the original Colossus Luminary programs, which were written by skilled engineers and then used in real life, so they were syntactically correct. So it hasn't seen a lot of use with invalid code. Like, nobody in their right mind actually does what I'm doing here. So let's try... No, that's not going to work. We're doing that in the wrong order. Is lxchange a extra code? Not an extra code. Yeah, we can get away with destroying the value on the stack because we just popped it off. So now I can just do index error. Okay, that assembled. Let's actually just do... Okay. Digit pair is going to be PROG, I think, which is M1, M2, which is address 11. This should... Okay, and poke this a bit. And let's just run it. Now, of course, it doesn't work. Were you really expecting it to? So this is going to take a bit of debugging. How have we actually got as far as disk use set digits? That is label f6. So break f6, run. Okay, we have actually got here, which is nice. So our loop seems to have looped. Whether it's looped to the right number of times, I don't know. Maybe it's just my code that's wrong. Writing compilers is an interesting exercise in doing two things at the same time. Okay, so... I want this one. Right, that should have copied all the parameters into w6. So let's do w6. And what we have are ob, which is our 11, 0, which is our sign, and garbage twice. Okay, so something here has gone wrong. Yay. Right, I've been going for a bit of a while and I'm slightly losing it and so I will go and get myself a cup of tea and be back. This is not going to be, I think, a single sit-down session. I am actually going to have to go to bed at some point. The thing is, I want to get this out on Saturday, so normally I just, while I'm doing one of these live coding things, I just take all day Saturday doing it. But this one is not going to be. Yes, I really am losing it. This one I've had to do Friday evening because I need time to edit and render it and upload it on Saturday to make the deadline. Anyway, cup of tea and let's see if we can make this work. If once we do have this work, we've actually broken the back of most of it. Everything else is night-rivial. I hope. But we'll see. Okay, cup of tea has been acquired. So let's see if we can figure out what's going on here. And I don't want to debug this in situ, so let's just do, let's just make a subroutine and go 10, bay n15 equals 2 and k n15 equals i divided by We only implemented the constant version of this and sub div. Okay. 34 Really? I fixed that. Apparently I didn't fix that. Stupid bad merge. I just wonder what else I may have lost. Yeah. The parser rules didn't allow for an empty parameter list. And I also need to do the same for the argument list. Okay. So here is our test div routine, which we're going to be testing. And there are no parameters. So we load i and store it. Then we load we go through the whole ludicrous division thing. Wait a minute. See, this is why you need t for these things. I never actually got around to storing the result. So this needs to be a not a ts an exchange because we just did a division. What does division do for overflow? The overflow is cleared. Okay. Well, we can use ts then. I mean ts and exchange you take exactly the same amount of time. So there's no real reason to use one over the other. Okay. Let's try this one. But I'm going to step through this code anyway because I don't trust it. Yeah. And here we store the result on stack, load it off the stack and store it into the workspace. That is f17 assemble cf17 run code extend So this is not interesting yet. And now we have loaded workspace into the stack. That is the thing we're dividing by which is now in a to put it in L Yep. L is two. Load the thing we are dividing which is two. Mask it so that we know that it is positive subtract leaving the result in a which is positive. That's fine. CCS takes us to the next instruction which takes us to the actual do division routine. Load the unmasked constant which as we're dividing by a value where these are the same it is like the same constant put it in L load the thing we're dividing so we've got 10 divided. That's not right. Why do we put it in L? It needs to be in Q because we need to use L for the other half of the division because we'd have to set that to 0 make your break f17 Okay. Load two we stick it in Q Q is now two. Load the thing we're dividing by stick it in a that's 10. Ah, right. The reason why it works, why X A works with A is because whatever it's doing to evaluate symbols is turning into a zero which of course that is the memorization of A. You have to use an ampersand to make this work. Zero L Do the division. Right. What have we got in A? That doesn't look right. What have we got in L? Two. The remainder. Uh should we not have a five somewhere? A is the more more significant word and L the less significant. I think that's an overflow. I think I should have put that value in L. So is there a I don't think there's a Z A instruction given there's a Z Q and a Z L. We don't need a Z A because we can just transfer from zero. Okay. So we actually want to put this it's the thing we're dividing by which goes safely into Q. This is the thing that we are dividing which goes into L. Then we load zero into A. So now we are we are dividing that. So A is zero L is our actual 15 bit value. Q is also 15 bit value. You see break up 17. I could use L exchange for this. Save one word. Okay. So in A we have zero in L we have 10 in Q we have two. Do the division in A we have five. Good. In L we have zero. No remainder. Good. That has actually done a division. And then we store the result in S17. Yeah. Now if I go we might actually get something and let's try that again after having prodded the diskey a lot. Okay. Well we think our division is working so maybe it's something else. Let's try breaking at set digits again. That is F6 go. And what does our workspace look like? W6 Hey what do you know? It's a three and a four. It's this three and four. It's worked. So that's correct. So yeah now we call diskey digit. We do the index is A now A is one. But yeah that's actually working. So why isn't it not actually updating the broken in F6? It keeps breaking in F6. Intriguing. I wonder if this is how this could be our watchdog timer. Shouldn't be. So if you break it for some reason it doesn't like breaking on certain symbols. I think it may be something to do with upper and lower case but not sure what's going on there. Right so we have actually hit this piece of code. So this should now keep I do not know what is resetting the system. This is supposed to keep uh keep poking the night watchman to prevent it from resetting the system. So I think it's not hitting go jam. So it's not restarted the system. So the only other thing I can really think of is it might be doing something dubious with the timer interrupt. So the timer interrupt itself. This is the actual interrupt entry point. We this is a double exchange. This stores both A and L into the address here. So that actually stores A into A rupt L into L rupt then we Q exchange store Q standard interrupt saving registers then we jump to T3 interrupt here where we schedule another timer interrupt. They come in every 40 milliseconds. We read the we read new job to keep the night watchman happy. We diminish a clock counter which we'll be using later. Diminish is fun. There's two op codes Diminish and augment. Diminish moves a number towards zero so positive numbers get less and negative numbers get more. Augment does the reverse. It's actually surprisingly useful and then we return from the interrupt. So we're hitting start up. We are not hitting start up but this thing switching to thread 11 makes me think that we are hitting the timer interrupt. We're not hitting the timer interrupt. That's very interesting. Is that wrong? 4000 octal is the start address of the machine in one of the ROM banks. So that's where all this code gets assembled. Now block 2 should have done the same thing but it might not have so let's just give this a try. So we are at Go Jam that's the beginning of the start up code. Which is 4000 octal which is let me check with my calculator. That is actually the right address. So 800 0850 OK. This all looks right. These values are I think known by the debugger. So here we can see that timer 3 is actually in the right place. So we've got the radar interrupt that's awesome. The joystick interrupt for the LEM and now we get into the actual code. How do you break timer 3? It's not a real symbol. This is never hitting the timer. I know what I'm doing wrong. I'm never actually setting up the timer interrupt in the first place. That's why it's resetting. We're never hitting the interrupt therefore we're never poking the night watchman. I don't know why this isn't doing it. And therefore it's never So let me just stick this piece of code on the end of my... There's only one thing to do. This should work now. It doesn't work. OK. We have actually hit start up. What's in time 3? 3 ffc OK. Time 3 is a it's a 10 millisecond counter that's not right. Oh yeah it is. OK. An interrupt occurs when it hits overflow. What is downrupt? Because we always hit downrupt just after we call main. OK. The AGC's interrupt handling is just like the rest of it. It's completely weird. Because the architecture is asynchronous various internal operations will lock out interrupts. So an interrupt will only occur when the machine thinks it's safe. Which may happen several instructions after the point where it actually fires. So that could well be happening there. So what is downrupt? Oh it's a downlink interrupt. Yeah. The downlink shift register is ready for new data. We have no downlink so that's not relevant to us. I still do not know why the timer isn't working. Is a horrible feeling I know what's going on. Which is I'm putting all my data in the wrong place. So I copied a lot of it from this code. So this puts everything into all its data appears at location 68. But the time three is at 26. So this is the memory map for the memory. So we get all our peripherals Alt-M-L-M only. I assume it's only with an altimeter. I don't think anything else is used there. New job is not a I-O a memory mapped I-O. It is in fact just a variable mapped by the operating system. But it's a privileged variable that the hardware knows about. Okay, so I think I'm alright there. Also yeah, I hadn't noticed that the timer is actually set up right here so I don't need to do it here. All I need to do is turn interrupt on and we're done. Okay. So there's two weird things that's going on. One is that we do not seem to be getting values into the disk key. And the other is the program keeps restarting. I know why. Octal always octal always octal. I hate octal. Let's poke this a bit and run. Okay. Well, something happened. It wasn't in the right place but something happened. Digits M1, M2. What we actually got was digits 3, 4 and 3, 5 which is this one. So yeah. So where are we calling disk key set digits? Let's put some tracing in. So the register is ignored completely. So disk key set digits here has got a hard coded octal 11 in the first parameter. So we've got C14 C15 and then our two digit values. C14 is 9. That's correct. C15 is a 0. So why is it showing up in the wrong place? I can only assume that my arithmetic here is wrong. Now one thing I need to know is I don't know whether the bit numbers in this are 0 or 1 based. Now I did count the bit field. So 2 to the 0, 2 to the 1 2, 3, 4, 5 10, 11 2 to the 11 is 2048. Let me check that again. 2 to the 11 is 2048. So we have done that correctly. So why has that gone into location 1? Yeah, I do not know. And I did, we did stop raw opcode and we did see that the digit pair was set correctly. Let's do that again. Raw opcode is f2 run x4w2 gives us the location of the workspace. So yes there we see that we've got diskey address 9 octal 1 not always octal I was right. This is decimal and this is binary. And if you're going to use multiple bases, put the prefixes on them. That may have written here. I am going to do a thing clear. Is there anything in address 0? There is not. I is not equal to 0 loop. disd set raw opcode i0 0 loop. So what that should do is clear all the fields in the diskey. Intriguing. Right, what's happening here is our program keeps resetting for whatever reason and it's writing the 3 and the 4 that we asked it to. This and this have not been reset correctly. Now prog here is m1 m2 and m1 m2 is decimal 11 our loop here starts at 12 and works backwards. The only possible thing I can think of is this multiplication is bad sign is 0 ok so this can probably be turned into a loop to be honest maybe yeah I think it could so we are storing we're writing to address 12 and 3 0s so we do the multiplication c3 contains 0 8 0 0 a contains 12 a contains 1 so 12 times 0 8 0 0 is not that. That's overflowed so what we have is a double precision value in a l that's weird drum file with this one. Let me check that documentation for the so I just spotted a lock code I haven't seen before okay that allows you to test for overflow which I don't want to do because I don't care ok the overflow is overflow adjusted. This means that if it's overflowed the overflow flag will be cleared and the value will be changed a bit single precision contents came applied by the content accumulator resulting in a double precision word in a l I've got a horrible feeling 11 12 13 14 15. There are 15 bits here 5 10 15. That means that top one is the sign bit oh dear. That's why I can't use a multiply because this is one's complement what we've done is we've multiplied the multiply is working but the high bit that would normally go into there is being bumped to the next word yeah I walked into this one because I've gotten that there's actually a fairly small number of available but the precision of a 14 bit word is really small right this explains a whole bunch of things I've been seeing with the diskey right the only thing to do here is to not do a multiplication at all and deal with pre-scaled addresses this would actually produce smaller code so this ends up being well we can actually do this Cal goal does it does its internal arithmetic 32 bits currently so this will actually produce a proper value but I don't know whether I don't know whether you will do anything useful with it because that value is strictly too big to fit into decimal ok that's done the right thing it's truncated the decimal value and kept the sign bit so this means that my clear is going to actually be ok we don't have sub from cons sub from is it subtracts it does that now we don't need one for add because add is commutative and the compiler front-end will just generate add cons both ways round but we can't do that for sub from because sub is not commutative so this is actually like simple code we are this is our constant this is our constant so that's a constant so now I think of it it makes very little sense to subtract a symbol from a constant so I should probably just turn this into an ordinary should just take an offset why is that failed? because that ok let's try that again but poke the diskey first ok that hasn't done anything at all so what I was hoping is this would set the sign bit so I would end up clearing 0 addresses 1 to 7 and also 1 to 7 to the top bit set wait a minute oh no that should su is ok let's actually try doing the sub from with an actual sub and see if that makes a difference diskey run still nothing let's just run this is my diskey still alive? no my diskey is not still alive it's just not responding anymore at all I need to kill the diskey oh also let's try actually running the bin file rather than the source ok now it's working it may have been working before to be honest so let's try running our program no it just doesn't work no it just doesn't work ok let's let's try that because I forgot that the thing is now pre-scaled so poke run ok well that's cleared these ones but it hasn't cleared this 0 that one is that's digit 11 which is ok this is working it's just that we don't quite we're not quite clearing the entire range so this needs to be we don't have do while we only have while so let's do this the rather than do stupid stuff with off by ones let's just do this will also force us to do another comparison no bad thing or apparently not so here we call first diskey set rocky code here we call the second one you can see the sub from here is negate I we can do better than that in fact we can do that here we increment I which as you see is a single instruction now here we are we doing here if I equals 9 yes what are we doing here right once this is done is is incremented the value on the stack oh yeah we then loaded back to I it'd be nice to be able to optimize this just increment I in place but we'll see the new code generator will be able to let you do that we load I stack it and then we yeah that loops not right where are we calling the ZF only here so some reason it is falling through this piece of code so I can see the CAE so the offset is comparing against must be 0 that's not a 0 that's a 9 that is a 9 double check everything do we even have 9 in the constant pool not that I can see okay my suspicion here is this is a compiler front end bug do we have any other BZF one two the second BZF this nonsense is X13 is the true case for the conditional which means it's this is the if then break so yep this is this is the break statement which is then followed by the false case which jumps back to the beginning of the loop okay great so the second time round we have sim equals 0 offset equal 0 so that has hit one of these so this is if the left side is a constant and the right side is not a constant we actually want to pass yeah this is backwards if this is backwards the others are probably backwards too so one value is stacked the other value is constant and it's selecting which one it's going to pass to the back end in order to yeah that's wrong so it's pulling the values out of the stacked expression node which of course is not a constant and therefore does not contain any of the stuff we need so let's try this and yes my new compiler back end or rather a mid-end is going to replace all of this stuff and everything will be better I'm sure it will rain puppies or something okay that's more like what I wanted to see what that's done is hit the second case here because we now need to do a proper comparison and this is going to be ccs again is going to be a sub followed by ccs so we need to uh take the thing on the left subtract the thing on the right the thing on the left is already in a we want to subtract the thing on the right which is a constant but we cannot subtract but we can't use subtract with a value in fixed memory so we're going to have to load it into ql probably l so that's load fixed constant add constant sim constant sim come off encode transfer to l load the thing on the left extend subtract wait a minute okay yeah that's a loop counter so we can't actually use this to do anything for us so we are comparing so it's positive first so that's false then positive zero so that's true then again for negatives so that produces this ridiculous code yeah I don't think even a people optimiser will help that now let's try it poke the diskey run not what I was expecting different it's basically resetting these to nines that means overflow because a nine is what you get when the bottom is the thing on the bottom when the actual digit opcode is all ones so this is all the positive ones and this is all the negative ones so by interesting this is a ones complement issue I'm sure wait a minute is have I got that horribly horribly wrong have I at any yes here we go no actually this is this is that's what I wanted okay I was wondering whether I was taking a twos complement value and masking it in fact this is hopelessly wrong no no is 12, 13, 14 bits that's the right size yeah if I take a twos complement value and I mask it in the host and then I add that as a constant then it's just going to not work now I am concerned about these two constants here here is my minus 2048 my plus 2048 I think these two yeah these have shown up correctly as negative and positive constants I think these two are likely to be one of them will be this db 11 by 2048 is 22528 that's this that's correct I mean it's masked into a signed value 1634 is just like 16k I don't know why we're using that to be honest this value is our pre-scaled diskey address and it is in fact passed through to set raw opcode unchanged oh dear I am so so unused to one's complement arithmetic ah so what we've got is 1 2 3 4 5 6 7 8 9 10 11 12 13 14 is this this is minus 0 what do you get if you add one to it? you don't get this so in fact what we've got is something like here is a here is a diskey address and we are adding the sign bit which is this bit here but we're not getting this because that's what you do in 2's complement what you get is this because in one's complement arithmetic you can't use additions as if they were ores yikes okay so this is going to force us to do the logic operations yeah arch logic op logic operations are all the same so we just have a single entry point and a switch statement except they're not the same not on the AGC because the AGC is so special okay I'm going to do mask first because that's the simplest case logic op mask so all we do with mask is we're masking with a constant so this is all happening in place where's that mask instruction mask and as a constant of memory indication bitwise into the accumulator so we load the value from the stack 12-bit address we do the and and overflow who is set according to the result of the operation how does and possibly overflow sign extended to 16-bits for storage in the accumulator does this mean negative values overflow let's just for the sake of sanity use exchange to store the thing back into the back onto the stack okay straight forward or the AGC does not have an instruction for oring values in memory what it does have is an instruction for oring values from a IOPORT luckily Q L and A maybe just L and Q actually are available in as IOPORT so in fact we can do similar piece of code we load one value into A we load the other value let's get this right we load the other value into L now we can't do that because that modifies the thing modifies the value being read we load this value into L destroying the value on the stack which is fine because we're going to write it back we load the constant into A we do the OR and we write it back from A flow is set to result in operation the source is a 16 bit Q register and it's not a 16 bit register full value ok that could overflow and we should be able to do the same thing for AND using RAND nothing to do with random numbers or objectivism let's try that AND, that's OR EXOR I'm implementing NOT constant 251 to ARCH logic op this is exactly the same thing but this time it's taking a value from the stack and we're just going to copy the same code alteration like so so we pop once and then modify the result without popping without popping or pushing so that's a pop because we are we are consuming two values of the stack and then pushing one so effectively we are just consuming a single item let's try that required extend ok poked diskey run has not helped diskey set raw keycode are we actually doing the oring so it should be ignoring all the ones complement stuff may or may not be negative but we don't care sign is positive left is positive right is positive ok, let's bench test this raw opcode set raw opcode ok now let's take a look at our workspace and see what we've got ok to be honest I do not know whether this debugger is doing the right thing here but that doesn't look right to me can I get binary out of this format letters are octal, hex, decimal half word hex I would expect to see the bottom bit zero is our multiply broken I have a feeling so we load one we we get one we do the multiply with c19 minus 2048 ok, so here is our multiply c19 77ff really, really it's getting out my calculator 2048 negative hex is f800 so that well on this machine in two's complement 15 bits it would be 7800 but that's suspicious can I get this to decimal ok, so in one's complement what we're looking at is this is 2048 in one's complement it would be this in two's complement it would be 7800 so 7800 it would be this what we've got is 7 00 f f so 7 7ff is that 2048 minus is that that's minus 2047 in two's complement where is that deck instruction it's in the pseudo-optic deck it embeds a single precision constant it assembles one 15 bit words it's the same as two-deck it has I have a feeling this could be a yule bug but that was 9 3f so so 9 3f trying to figure out where that actually lives in the binary so 3f is 127e minus starter s of 0800 is not there if it contains no decimal point applied b minus 28 I have a feeling deck might just not work with negative numbers what's the description of oct an octal constant places a 15 bit word 5 digits okay alright I'm gonna do a thing which I should have done before which is to add a helper the purpose of this is to give only one place we have to change when doing this stuff oct percent wow let's see if it likes octal better than it likes decimal so again this all boils down to the fact that this machine was designed for numerical computations using you know decimal values so it's primarily arranged around dealing with numbers that mean things and have units rather than just integers so having all your values being minus 1 to 0 decimals not floating point fixed point with a constant scaling factor actually makes a whole lot of sense given what they're doing but I think it might be upsetting us I did discover that the AGC was used in things that weren't Apollo I found a reference to a fly by wire aircraft which used one it had a diskey behind a hatch somewhere in the back of the plane and also in a remote operated submarine but given the enormous cost to them which I don't know what it is but they were all handmade and enormously complex ooh that's not right that's not right at all unsigned let's try that okay 5400 looks suspicious but I don't see our weird numbers so maybe it's going to work diskey run I forgot to actually yep nope still wrong so maybe that wasn't the issue at all not sure where it's getting its value from oh the multiply of course actually it's this yeah um I'm probably going to need to call this a night and complete the rest of it tomorrow because I'm definitely losing focus as you can probably tell if you're actually saying extend I do not intend to do these live streams live coding sessions on a terribly regular basis but after doing the compiler I suddenly realised how good a match cowl goal was for the AGC which I've been reading about anyway after watching curious marks videos on the AGC restoration and I got intrigued by the architecture which is crazy and I like that sort of thing in a computer system that is still 777FF and my C19 is a negative octal number hey if if you doesn't want me doing if you're not turning uh negative numbers into valid one compliment ones compliment number let's just do it for you so this is going to be since that sets the sign bit and hopefully generates a valid one compliment's number okay so I can check our model so C9 is zero that wasn't what I expected and yeah multiplying by zero gets zero so did that truncate it so this is a minute that is zero C9 is zero yeah that would be a good idea C19 is right that's what I would expect a ones compliment number to be so what's A is overflow what's L yeah that's kind also kind of what I would expect it to be the the top bit set means that's an overflow condition so when we store it into S1 what we actually get is this which is what we expect from multiplying one by that okay so let's try this program to wake it up and run and drinking we still have nines here these are 11 and 21 which is 8 and 8 and 5 and the flickering up here is because that program is continually resetting which until we actually if you actually want to play a game we have to do something about that try this to see what that does okay we don't have empty statements yet so yeah we are still resetting something is wrong somewhere and I don't know what it is so looking at our code here is our second code here is our multiplication C19 C19 contains this octal value guess how calculator again octal 4400 in hex is 4800 which is what we expected and let me try running the Piper referral again because that will actually set the let's change the program to something with numbers on it let's try that one run our test program put this key so it cleared this one this time it didn't clear this one yeah okay I bet I know what's happening there and the reason why it hasn't cleared that one is because that is this digit here so clearing it involves setting all the lower bits to zero so this is actually negative one this value and we're not going to get there by multiplying this so we are just running into plain and simple issues with the sign bit getting in the way of arithmetic so what we have to do is just do it the old fashioned way using repeated code and we get a segmentation fault this is actually the first time I've used a subroutine inside another subroutine so let's actually just much push value but it has optimized the way everything useful for debugging so arch push value is actually loading the contents of a word in the workspace so it will always be a var let me just change this to not do any optimization hope that makes a difference okay so the symbol is that looks kind of valid umu.var.sub oh wipe is not owned by anybody why is wipe not owned by anybody this creates a subroutine subroutine's parent is this where do we add it to the symbol table where do we add it to the symbol table I know we are adding subroutines to the symbol table but we can't find it oh new ID adds it to the symbol table okay right wipe is not a variable wipe is a subroutine which is not the same thing and the reason why it's crashing is we're trying to push the value of a we're trying to push a push the value stored in a subroutine that makes absolutely no sense and I don't know why it's doing it so this is going to just delve a bit into compiler debugging what are we doing where are we this is all the wipe instructions so what we've got here on the left is all the tracing by uh the bison parser as it parsers things which I'm going to use to try and figure out exactly where it's going wrong I know what it's doing it's I don't know if there's an easy fix for this so what's happened is we have a rule in which call subroutines where if it sees an ID that already exists followed by a parenthesized list of things then it must be a subroutine call we also have a rule that an L value can consist of a an ID that already exists followed by a square bracket so it should have read the old ID we should be able to see that here yeah it's read the old ID and stacked it it's read the it's read the ID and stacked it it's in state 38 which is state 38 has actually decided this is an argument list so what's actually going wrong is line 339 of the parser so this is kind of what I expected it got here from line 213 which is an L value from line 249 old ID 274 argument list yeah I think it is clearly trying to use wipe here in the context of a variable but I don't know how it's getting there so this is this is here so here's the sub here's the ID it's just created a new ID yep new ID and then we go through the actual subroutine definition itself and eventually we will see a red token parameter thing next token is ID oh it's here oh okay yeah I should fix that we should only well you shouldn't be able to use subroutines there at all there should be a type check for it and we really shouldn't add this to the symbol table until the end sub gets hit that still fails why is that failing now parameters does it a parameter list parameters are the things in subroutine definitions apparently it works now weird very weird anyway poke the diskey okay it has successfully cleared the diskey good this is a genuinely a really good start why it hasn't set no way that still should have set that register I think this is more reset time up issues so I believe that we should see yeah this flickers occasionally in fact it says 04 is a bit disturbing anyway I am going to commit this and I believe yeah okay I seem to have screwed up my GitHub repository but I can do that and I think I will try and tackle the reset issue tomorrow we are generating pretty good code but we are generating capable code that is actually doing what we wanted the ones compliment stuff is reekish yeah each of these wipe calls is actually turning into a reasonably small number of calls of instructions which is nice I might also need to do some experimentation with one compliment arithmetic in this thing just to see if it works the way I thought it does so for example taking this value and actually negating it using the actual negate instruction and seeing if this matches my expectations compliment no that does not okay subtracting from 0 there is probably a negate instruction somewhere or you could simply load 0 and do a subtract that would be three instructions kind of tempted to do that now actually let's do that now minus i cal-gold does not use single equal signs anywhere if 31 assignments are codon equals comparisons are double equals right here we go so there are no parameters so we just save queue we load our constant stack it we have 080 in a we write it to i we load it out of i we load our 0 we do the subtract and a contains okay right I wasn't that completely I should have tested that ahead of time the was actually doing exactly what it should have been and I was an idiot still it's nice to know that things are proceeding as normal because we did this refactor this now becomes easy to put back where it was and in fact since we gave up on trying to do negative numbers this should now work a lot better you poke the diskey run it without throwing my mouse over the table with the right command there you go blank diskey and a 34 good good I am still very curious to know why that one's complement number did not have the bit pattern I expected that was not how I thought one's complement worked you know what where are we going to put it in register 1 that's 11, 12, 13, 14, 15 11, 12, 13, 14, 15 so this needs to go into diskey register 6 that will be around 6, 7 and 8 6 7 that's not right 0, 1, 2, 3, 4 just the way we like it and we can also do we can do signs so sign 15 equals 0 if value is less than 0 then sign equals 1 value equals minus value and we don't want to use positive we don't want to use pluses so we just want the 1 minus bit which goes into digit 14 on 6 digit 14 so that will actually display a negative number now I hope greater than const did I implement a sure I did a CCS that one there equals const so this is actually going to be pretty similar code if the thing we're comparing against is 0 then we just need 2 just need this bit and you got positive so yes greater than 0 is true 0 no it is not greater than and all the rest are falses if we are comparing with an actual value then we need to do the subtraction so yeah it's all of this again and in fact that can go here I hope and all the others will be the same code so it may actually worth trying to refactor that run and that's not right let's just try a positive number again because this could well be negative numbers making all the signs go funny run well now we get a why are we getting a negative sign there sign is 0 ok the comparison is wrong that's why did I get the order of the operations correct greater than 0 0 greater than 0 0 less than 0 0 so am I doing this the wrong way around it is comparing right no not right it reads the value out of the memory location it then diminishes it as described earlier but then the jump happens on the original values the original value so the diminishing is irrelevant so over here is the compiled code for doing this so here is our if statement we if the value we load the value we stack it we unstack it if it is greater than 0 then we follow the true path greater than 0 I think this is another front end bug because this stuff is painfully hard to get right what happens is that the back end only has to implement greater than or less than and all the other operations that is less than or equals to greater than or equals to comparing with constants in any order are synthesized so I bet that I am messing some things up so less than 0 is actually going to hit this code this code path so the left hand side is not a constant but the right hand side is so that is the equivalent of doing a greater than in the other direction no it's not this is the one so if the left hand side is the constant then we flip the less than to a greater than and flip the order of the operation so it's valid and I think the same thing applies here so this side is a constant this needs to be less than this needs to be greater than okay less than const false label this is going to be true label okay that's better let's try this again with a negative number run that's not working okay there's some work to do there and actually I will just try inhibit alarms broke this key run yeah okay inhibit alarms turns off all the things like the night watchman so we can actually get stuff done by just doing that it's cheating I don't want to like disable bits of the computer even if it isn't emulated computer hmm okay okay so I need to I definitely now need to go to bed I do wonder I think I started this at about eight so five hours seems longer than that just wondering how long this is actually going to take as I said we have actually broken the back of it it's now just a matter of filling in the holes and debugging the remaining weirdnesses such as the whole whatever's going wrong with negative numbers but the fact that we are actually capable of doing this this is code is actually doing real work now and that it is actually working is great this shows that we are achieving things I will not see you tomorrow but hopefully you will see me tomorrow or in about five seconds good night good morning it is now the next day I have coffee let's get this done so the way I left it either last night or several seconds ago is most of the code works we have a problem with negative division our program is capable of writing to the diskey so we need to tackle what's going on with the negative division once you've done that I think we're pretty much good to actually go ahead and write the game but before I do that let's actually just go through and write the code to actually place numbers on the diskey in the right place while making coffee I did actually tackle the I did actually add else support to if so that will help although not here so register one is going to be this row which is 11 to 15 digit 11 to 15 is 6 7 and 8 yeah that one is correct and exit and if if register is 2 then it's going to be the register below that which is 21 to 25 oh pants okay this is problematic we cannot update digit 25 without also updating digit 31 I need to store a copy of the state of the diskey in RAM blast indeed okay fantastic so what we're actually going to have to do is to see if only we had the ability to actually do this multiplication in code so the whole complex ones complement mess that I went through yesterday is because this causes a sign bit overflow so there is a way of to bodge this it's really nasty I think we're going to have to do I have to so what I'm thinking of here is changing probably set raw opcode so that this maintains a state of the diskey and if you pass in an opcode of say minus one it simply copies the state it already has rather than rather than actually writing the value given so what this would do is maintain an array containing the what it thinks is the state of diskey I think that's over complex also actually updating a value would require us to disassemble the old one which means more more logical operations I think we actually have all the functionality to do that but the main thing it needs is just passing in a simple address won't work the simple prescale address we're going to actually have to pass in which word we're going to modify and then construct the address field so what I'm thinking of here is that we can actually do this by scaling the address by 1024 which doesn't trigger the sign bit and then doing a left shift by one the AGC's ability to shift is weird it doesn't have an opcode to do this and remember that even though this is the wrong page on this one even though it's got a double opcode double is not the same thing as a shift left in one's complement arithmetic what we actually want is there's a special memory mapped register these three we've got cycle right, shift right and cycle left when you write a value to one of these memory locations the result that actually ends up in that memory location has been shifted now there is no shift left so we would have to cycle left and it is a 15-bit cycle so that what we would do is we can either put the address in the bottom four bits and then cycle right four times thus ending up with the address at the top or we multiply the address to shift it up to the left into BCDE and shift left by one and cycle left by one giving us BCDE here and the sign bit which should be zero gets wrapped around to there I think I prefer the multiply and cycle left approach in order to cycle once we need to write the value into the cycle register and then read it back out again before we can write it back to the register so it's it just reduces the amount of inline assembly if we do a multiplication and I want this and I'm also going to need to actually place I wanted cycle left is in octal 22 cycle left is 22 so let's do that there's always something making it more complicated it's always more complicated so load the opcode write it to the cycle left register load the updated value which won't have overflowed because the cycle left register is 15 bits wide and therefore cannot overflow which means it is safe to do TS and we're just going to simplify some of the logic by doing this so data will contain the actual the lower bits so we now want to or in the value of I was thinking of just doing the whole thing in inline assembly but actually because the logical operation is that weird IO channel based thing I'm not going to I'm just going to do it like this the brute force way so this should generate the high bits of the address this will then or in the digit data and then we write it to the IO channel so this should then allow us to now we should be able to do 15 equals 1 while I is not equal to 13 loop this key set row opcode I and loop I plus 1 does that build simple return not found we haven't implemented this so we need to do an early return from a subroutine there's normally two ways to do early returns from subroutines you can either just admit the return code where the return statement goes if you're on a system like the 8080 then the return is just a ret statement so you do it there or we jump to the existing epilogue as as our return epilogue is three instructions we shall actually do it in line oh, simple return not found I never got random adding that to the parser okay so let's do that now where's our call omit call I am very inconsistent about whether I call things omit or otherwise so return just does this add this to the prototype underneath omit call here we need to add the token to the the lexer add the statement the parser return is easy okay so return is not used but not defined as a token let's add it as a token okay that's our generated code which may even work and now we want to step through and test it so we want to we are interested in diskey set or opcode break to poke the diskey found it okay load our parameters as before right, we load the address here's our inline assembly that we actually care about we write sysil and then we do the multiplication by 1024 giving us 1800 in L which is hopefully what we expected so that then writes it to the op variable we then load op back we're now into the inline assembly so 1800 is in a we store it to sysil and sysil now contains if I type it correctly 300 is that what we wanted hex 1800 times 2 got pocket calculator out again 1800 times 2 is indeed 300 so make sure we have the right value in A yep that's correct okay let's just now what's that done in a diskey now why do we have 04 there do I still have my negative number in the no we don't okay that should have actually done 1234 so it's still not right to leave we also should have hit diskey set rule op code way more often than we did here we hit it like twice fantastic twice three times so is our loop wrong so this is our set rule op code here we do the shift and we write it into the workspace variable into op we load op we load sign and shift it with multiplication store it onto the stack we load left and shift it and store it onto the stack we load right and shift it store it onto the stack we so now we're just going into the oars so we load right we store it onto the stack we load that into L we load the next thing we want to we want to all together we do the IO channel read and or we now have the result in a you write that to the stack load that into L that looks all reasonable so is our loop diskey clear is our loop wrong so I think this may be the first time we've actually done a loop against a constant that wasn't zero here if I equals not equals to 13 and it's possible that I managed to break something so here we set up our I to hopefully one C9 is one C9 is one here we do our actual loop we load I stack it we load our constant into L the stack constant we want to compare against we do the subtract and then we have our CCS followed by four jump instructions so we have positive or negative we want to compare against zero right we are doing a yeah we are doing a comparison against a constant so once we do this attraction the result will either be zero or not zero and that's what we care about so why are we using CCS and not BZF so equals constant we should be using BZF here so I think this is just wrong so if non zero zero non zero zero I don't know we could totally be using BZF here but this is not working something is not working correctly so anyway let's take a look at the code again so this is non zero so that is the true case oh I also came up with a way to do multiple labels at the same time which I believe I may have just gotten wrong yes I have that's what's wrong with it it's my multiple label code okay which I did offline over coffee so what I was doing here is if you we have the problem where every label has to have an instruction attached to it otherwise YAGC crashes so if you emit two labels in a row then obviously they can't both have instructions so what I did was I emit a just an equality assignment to say that the two labels are going to be the same so before we update the label we say that the old one is the new one yeah yeah I think this may be interacting poorly with label aliasing because if you emit an actual label followed by a label alias yeah I'm going to have to go back to my horrible supro hack because we actually have to emit the the label itself if there is a label set sub row then force it to be emitted I believe that may also be another yule bug to be honest because x19 is defined to be x20 x20 is defined to be x18 no actually that's completely valid x18 is a real thing but we are never actually emitting the label here ok now what we've got is x19 is sub row so it's set to be this location ok let's try this right ok that works so now we need to start actually creating our diskey buffer and our diskey contains 12 words of data running from 1 to 12 so we need 13 here the old data is going to be diskey buffer address the sign bit we're just always going to update so if left is not minus 1 then then we want to pull the old value out of older data now actually let's just do if yeah if left is minus 1 then then we want to mask off the value we're actually looking for which is 5 1 0 2 3 4 5 left equals left times 32 this is just going to pre scale left if left is minus 1 we use the value from the old data otherwise scale it and likewise we do right and likewise we do sign sign is not minus 1 then sign equals equals sign times 1024 so all we do here now is we all together the address part sign left and right which have all been scaled we write it to the diskey and then very important we update our buffer with the cached value ArchD reference we are loading from a pointer pointer for the first time so what this does is it dereferences the thing on the stack and we know how to do this using the index statement followed by a CA0 however we do know that index does not like addresses with offset so we're actually going to load it into A followed by index A followed by CA0 so that should have read our value and it came out of memory therefore TS is safe followed by TS to write the result back onto the stack total stack change is none we will actually do a pop and a push just to make it explicit what's going on plus there will be other stuff we'll be doing with pop and push later maybe probably not essentially what we would do there is omit annotations to the people optimizer to say that we have popped this value of the stack it is never going to be touched again so the optimizer would be allowed to do more optimizations okay so our diskey set raw opcode is huge yeah we're comparing against minus 1 in the worst way possible here is however oring everything together so we are actually loading everything onto the stack and then oring it that's not right I mean it will work but it means it's everything's right associative so we actually want ooh I never got around to adding my logical operators to the precedence table that would be why what precedence do these things have cal goal precedence is mostly stolen from C and is like C is incredibly unintuitive okay that should be better right we are now here's our scaling here's our oring so we start with op and then we or in sign and then we load an or in left and then we load an or in right this is all terrible and here is where we actually write to the diskey here is where we write back to the it's this line of code here so we load address stack it C3 is going to be the symbol referring to diskey buffer here we can yeah we could use index to generate this code but code generator doesn't know how so yes we load the address of diskey buffer we add on the offset we now load op we do the index to write okay but does it work then we should see no change diskey run we should see no change I said what's going on here then this time we're using and so possibly that's gone wrong possibly our conditionals here are wrong yeah this seems plausible those nines indicate set bits and I think set bits come from let's try that okay that's better that is what we expected so now we've done this we should be able to start work on actually setting our values so if we are writing to register to which is 21 to 25 what we do is 21 to 25 we want address 3 left hand bit only and the right one is left unset so we pass minus 1 in there so this would be 2 3 goes in 5 I don't think I've got that right 4 3 2 1 0 and the sign bit we were looking for the negative bit 2 minus goes at address 4 which is this one we do that okay let's try this 4 5 the diskey run okay we've just managed to flip the top 2 bits yep but other than that it's worked right register 3 this is the one at the bottom of the diskey is in 31 to 35 which is addresses 1 2 and 3 so it's the same pattern that we used for the first one 3 4 2 and minus 1 because this shares with this and the sign bit is in 3 minus is in register 1 so sign goes here right register 4 now register 4 is going to be our prog up here which is m1 m2 it's only 2 digits wide so this is straight forward it's address 11 with no sign bit 5 and 6 are going to be the verb and noun respectively so v1 v2 is address 10 n1 n2 is register 9 and the sign bit is unused throughout so that's straight forward 10 9 okay so is this going to work 6 7 8 9 register 1 2 2 I actually don't want to use I'm looking for unique sequences 3 2 4 3 right moment of truth time fire up the simulator poke the diskey and run okay that's not right right we've got 0 1 2 3 4 which is correct we've got 0 2 3 4 5 which is correct we've got 6 7 8 9 with no 0 that is not correct 6 7 8 9 register 3 so the top digit is not being placed in the right place so this is the first time we're actually reading a value out of old data that's ended up as 0 okay yeah so we're passing minus 1 into set digit which is then using the lookup table so what we're actually going to have to do is if that is not equal to minus 1 so that if the user has passed in minus 1 to either of these we don't do the conversion from a numeric digit to the video RAM character code run it's interesting I wasn't expecting that so what's gone wrong here if left is not minus 1 this should be, this is exactly the same code we've got up here so diskey set register that's diskey set digit so what are we doing we load left we stack it we load our constant we put it into L we load the thing up the stack we do the subtract 13 is our false case 14 is our true case so 13 wait a minute this is wrong there should be a label immediately after the condition there should be a label a true label should have been emitted directly here but it hasn't it's just gone straight into code why has it done that so I bet that what's happened is there's inline assembly immediately following the equals constant and the inline assembly is not honoring the label so the label is getting emitted after the inline assembly so 13 is the true case which is going here though it should be here right that's straight forward because we have to do this bit like manually so if there's a label put it there otherwise otherwise just emit spaces so where is our, I can't find it diskey set register diskey set digits we're looking for here we go x13 so that should have put it in the right place so after the inline assembly we then jump to the exit label for the conditional which is x12 x14 which is the false case up here as an alias for x12 and then we have x12 immediately afterwards ok now let's try it put the diskey run good 06789 here however these are zeros and they should not be zeros this looks like I am reading the wrong digits yes that was getting the highest the most significant digits from the decimal value rather than the least significant digits the decimal value of the diskey run there you go we have successfully updated the diskey with the right numbers good now we have to deal with negative numbers of the diskey run as you see this has this has gone wrong and it's managed to write a whole bunch of it's managed to light a whole bunch of alert lights we have gimbal lock this means that the surprisingly cheap inertial guidance unit in the LEM has been pointed in a direction that it couldn't that doesn't work with the gimbals have all locked together and now the spacecraft does not know which draw it's pointing they could have avoided this by adding an extra gimbal to the system but they didn't which is very odd our altitude is too low and our velocity is too high this will be I know what's going on here I think I know what's going on here I think the value is out of range which is then causing overflow and possibly reading garbage here I bet what's happening is the sign is not what I expected alright let's go here so that the first call to diskey set register gets negative value and step through it and see what it does diskey set register gigantic gigantic subroutine ok break f11 run so we load our parameters we that's interesting so in the excuse load one parameter load second parameter ok now we're getting on to the actual code we're just comparing sign and value ok we're assigning zero to sign and then we do the value comparison here so here is where we assign to assign zero to sign in our stupidly complicated fashion now we are testing the sign of value so that which is I assume the correct negative number we go to the true case meaning that it is less than zero which takes us to here now we set one to sign yeah C13 is one stack unstack sign to sign here is our workspace so zero one two one the sign bit is set we now need to invert value by subtracting it from zero so load value into a stack it load our constant zero perform the subtraction the result in a is 0 42 which calculate again for D2 in decimal is one two three four that has done the right thing so we then write it back to value and then proceed with the rest of the code so so we are now actually following the same logic as the positive case we know that value cannot be negative therefore what's different the only thing that's different actually is that sign so when we are in sign are we somehow managing to mangle the resulting opcode I can't think how let's see what we get to diskey set raw opcode then diskey set raw opcode write f2 okay push parameters and our state looks like like address 6 sign 1 left hand digit garbage right hand digit 6D I'm pretty sure that's not a valid diskey character code yeah that's way too high okay that is obviously completely bogus let's go look at diskey set digits f9 push parameters these are negative why do we have negative digits there we know that value is positive because we tested it unless this is failing to write back correctly let's get back to diskey set register then and in fact we can break out we did actually check to make sure that the workspace value is correct it is the positive 1,2,3,4 value so can we get decimal out of this apparently we can't that's not right have I been hit by the dreaded TS is skipping the next instruction on overflow issue so we do the subtract the value in A is 0,4,2,D we then overwrite the value with the unchanged one we are reading rather than writing yeah okay what's happened here is that our sub routine is yeah we are actually completely forgetting to write the value back to memory to help and because we have done a subtraction overflow might be set so we use exchange which ignores overflow okay that should work better now so let's poke the diskey fantastic we have a negative number good right this is a milestone because this means that we have enough logic in place to actually start writing game code or at least we have enough logic in place to start actually doing game specific stuff now our lunar lander game is going to be the extremely simple you have a spacecraft dropping in one dimension you have an engine you're accelerating down due to gravity you need to make sure that when you reach altitude 0 your velocity is below a certain amount now in order to control the engine the player is going to have to operate the game somehow so what are we going to use as an input peripheral well we are running this on a lunar lander remember and lunar landers are intended to be flown by hand and what they've got is a joystick because the LEM was fly by wire the astronauts controlled it by using a flight stick which then commanded the computer to operate the attitude thrusters to rotate the vehicle and as a result the LEM joystick is actually memory mapped and I have a where is it ACA3 is using my flight stick which I have over here where you can't see it so if I push the joystick you can see that the ACA3 emulator is noticing the fact and we can do this in all directions we're actually only going to use forward pitch to operate the engine so we need to read the joystick to do this we just read the value of this memory register but and it took me ages when I was investigating this to realize but you have to enable it first by writing stuff to channel 13 so let us try and read the joystick pitch first we need to enable joystick we need to enable it by setting appropriate bits in channel 13 which is bit 8 and bit 9 so I believe in their parlance this is bit 1 so 1 1, 2, 3, 4, 5, 6, 7, 8 2, 3, 4 nah that's not right for that load I extend right to port 13 octal almost always octal but in this case actually octal so enable joystick begin an infinite loop we want to read the value from register 32 RHCP I believe I set a symbolic constant for that we then want to write it to memory and I'm using exchange because it might overflow pitch and then we say diskey set register 1 to the value and let's see how that goes spoke to diskey and nothing happens now I have slightly run into this before where updating the diskey too often can cause it to crash I don't believe that's happened here it should at least have cleared it so now let's just try commenting all this lot out and trying that again so if it turns out that updating the diskey too often makes it unhappy that's interestingly different this is happening because our main subroutine contains no arithmetic operations therefore there is no stack so why is something actually referencing S1's stack because we are calling a subroutine therefore we are passing the pointer to the parameter block but we only need to do this passing parameters yep so parameters is non-zero that will also generate better code poke the diskey and run I think that was the that I'm simply updating the diskey too often let's enable the joystick and make sure that didn't have an effect poke the diskey why is the standby light lit up probably because this constant offset is incorrect I think it's like this I think that bit 1 is this one therefore bit 8 is this one because I believe that there are some lights connected up to that particular IO channel poke the diskey and run okay the light's gone off which is good now we need to update the diskey less often I actually thought of this earlier and in our test framework in the timer interrupt we actually diminish a value called clock I was going to use this to do timings so essentially what we can do is we can write a value to clock every 40 milliseconds until it hit 0 which point it will stick at 0 so if we want to do a pause say half a second we set it to 12 and then wait until it's 0 however our clock interrupt is not actually firing for reasons which are not particularly obvious but anyway let us write the code which is every half second load value we're going to wait for 500 milliseconds now we do load value into load value into A transfer into I actually I can just do while I is not 0 loop and loop however this is not actually going to work but let's try it anyway just in case so here is our main program we clear the diskey we enable the joystick we load the pitch value hopefully store it into the workspace we call diskey set register to actually display the value to the user we pause and we loop so poke the diskey run the simulator first then poke the diskey and run so yeah we get a 0 because that was the pitch and yeah if I push the joystick nothing happens so let's so we're going to have to wait our timer is firing our timer is firing why is our timer firing now when it wasn't earlier so this should actually be working then so what's in clock clock is a lowercase symbol and therefore the debugger cannot see it so let's change that clock and clock is defined here poke the diskey and run so clock is a set to 2 so if we diminish that then clock is now 1 clock is 1 clock is now expected clock minus 1 is that's wrap round that is definitely not what I expected have I misunderstood how diminish works diminish, decrements a positive non-zero value or increments a negative non-zero value if the contents of k is greater than plus 0 it's decremented by plus 1 so 6 would become 5 plus 1 decrements to minus 0 I know what's happening with these numbers I am no actually so what I thought was is this actually interpreting the bit value of 1 followed by 14 zeros as a 2's complement number and therefore showing me the wrong encoding but I don't think that's happening I think this is interpreting it as a unsigned 16 bit value so I think this is actually a 0 but it's not the kind of 0 I expected anyway we return from our interrupt we are at x53 we should at this point be spinning waiting for we're in pause spinning waiting for the clock to be 0 the previous value of clock has been loaded into I so we are here we load clock into A has overflowed I yes because the top bit is set I wasn't aware that could happen right CAE the overflow is cleared unless K is accumulated or the Q register if the source order is 15 bits will be sign extended to 16 bits when placed in A that contradicts this have I misunderstood how overflow works the 16 bit is present and there is no overflow the 16 bit accumulators duplicate of the sign ah ok there's not a specific overflow bit the overflow is detected by the top two bits being different so as this is in negative number with the sign bit set then bit 16 is a copy of the sign bit so that's correct that's perfectly normal it hasn't overflowed good we write we write the new value into the workspace we then loop around again we have finished our loop that's working that's working but it's actually not reading pitch values so I have a feeling I may not be I did actually make this work in testing so I do know that the pitch works 42RCHP we have set this correctly ok that is 42 here we load it brake 57 so poke the diskey x57 push my joystick forward with one hand or typing run with the other ok we should have loaded the value into A which is 0 I believe that I haven't enabled the joystick correctly the count is supposed to update only if RHC counts for enabled bit 8 of output channel 13 is set and when the count is requested bit 9 of output channel 13 is set and that's actually did that connect to the yeah that has connected let's just set lots of bits that should probably light the standby light again and this has not done anything so that should have actually called diskey set register and displayed a value even if it's 0 so you can see there's a pause when I continue this is the half second delay so it is actually cycling through the register and something should be happening so our diskey doesn't look like it's crashed so why hasn't anything appeared on the screen so this should just count we have a program so we know that our program is capable of writing to the diskey fine but we're not reading the pitch from the joystick ok so our joystick port is at 42 octal which is in hex 22 decimal 34 push joystick receive no numbers fantastic ok I did actually make this work in testing so I do not know why it's no longer working in testing teen set that is output channel 13 label joystick is at f30 are we actually writing the right value f30 is here that would be 3a0 with our various bits set we do actually appear to be writing the correct value to the joystick enable port I am going to investigate this offline because this bit is not interesting to watch so back in a moment well that was awful I've made it work see if I push the joystick forward there is a number appearing the diskey never pull it back there is not a number so I stepped through virtual agency itself and did a bunch of debugging there and I discovered a whole slew of different problems one of them is that the the RHCP memory address only gets updated with the value corresponding to the joystick position each and every time you write to channel 13 it does not get updated in real time so I can't just enable the joystick and then let it run I have to call this every time I want to access it other than that is just a memory location the other problem is that I had misunderstood the way that virtual AGC is configured now the diskey and the joystick are two different servers which connect to the main AGC simulator and you have to specify a port number that they connect to or use the default and I didn't realise that they have to use different ports normally you can have multiple clients listening or multiple clients connecting to the same port and server and everything works fine but not this case and this is why I was having so much trouble with the diskey work reliably because every time I started the simulator the ACA server running the joystick and the diskey server running the diskey would fight for the same port so sometimes the ACA would win and the diskey wouldn't work but I would be getting joystick information which I could see in the debugger and that was dead confusing sometimes the diskey would win but then we wouldn't get any joystick data so that's now done so look, numbers and it's all happening in real time and everything is fine so now we can actually write our game we've got all the pieces, all the logic we can make this work so we're going to want a game loop we're going to want several game loops actually but let's just start with the outer game loop where we are going to we're going to run the game loop every tenth of a second so that's every hundred milliseconds so that's roughly every four clock ticks so tick calls three this is just so it runs at a consistent speed and then we write that to and then we run the game tick and then after we finish the game tick we then wait for the clock to expire this means that one game tick can actually take as much time as it likes up to of course slightly over a tenth of a second and everything is fine so what do we do inside game tick the first thing you want to do is to read the joystick and figure out what the how much thrust the user is giving our virtual spaceship so we read that and just so the user gets some feedback we're going to stick this in the the prog field up here which is one two three register four so we do this and hopefully this should work simple I not found here what was I oh yeah yeah I just want to do you know let's do that bit properly there we have a program we can run it so we poke the diskey a bit just to make it connect and we have read a thrust value into prog push the joystick forward there we go and of course there is no there is no minus sign for prog but that will do just fine so now have some game state what state do we have we have the player's altitude which is going to start at one thousand meters but we're going to scale everything by a factor of ten to give a little bit more precision we have the the vertical velocity we're going to be nice and start that at zero we have the amount of fuel which is a thousand units so every clock tick we want to display the thrust value the altitude undoing the scaling the vertical velocity undoing the scaling and the fuel now we want to make sure that if the user has run out of fuel then no thrust is available and every clock tick we update the altitude with the velocity and let's have negative velocity for down just to be a bit more you know sciencey and every clock tick of velocity decreases by we've scaled by a factor of ten so that's actually ten centimeters per clock tick we have approximately we actually have three forty second clock ticks so each clock tick is 120 milliseconds so we have 8.3 a second so we want an actual physical value we're going to have one meter per second squared which is about a tenth of Earth's gravity divided by 8 if we just subtract one that will give us a fairly low gravity that will be fine this is not scientifically accurate poke the disk okay now here we can see our velocity slowly increasing why did that not start at zero that's interesting and that's garbage why is altitude changing when I move the thrust lever let's put raw figures here to see if that makes a difference that sounds like more arithmetic bugs sadly well this is minus nine five four six definitely minus nine five four six we've not modified altitude altitude is set right there the fact that that changes is extremely disturbing I believe that what's going on here is something wrong with actually setting the disk key so let's change that to three and see if that makes a difference no it's not okay let's look at the code probably a code generation bug so one game tick we don't need to initialize thrust here is where we actually read the value here is where we clear the thrust value if there is no fuel left here is where we to is where we update the disk key with thrust value here is where we set the altitude value the w1 plus 13 is the outer subroutines workspace do we actually have enough precision for 10,000 in hex 2710 I think we are fine yeah well let's start commenting bits out until something happens still nine nine five four six interesting I think we don't have enough precision my maths were wrong okay what is 10,000 in hex thousand hex is 2710 that is 0010 0111 001 0000 that should be fine the sign bit is on the left and zero so that's not actually problematic in any way that's just reading the wrong value 1000 work the thing is we had this working before we were displaying numbers in all the registers okay a change we've made has broken something so let's put this back the way it was put this back the way it was change this comment out all of this and do that works that works fine we get the correct values in all the various sorts I wonder if this is because this is reading an up value it shouldn't be there is no difference between an up in cal goal there's no difference in an up value in any other global variable all variables are global that has read 8200 instead of 1000 that's a still read 8200 but 1234 worked did we just get lucky with the with our test constants I think we did and it's actually just broken wow 1230 1200 yeah 1000 just doesn't work okay what's going on here is because we are dividing one by 10 okay this is the horrible AGC division instruction producing gibberish that's what it is so the I think it is so we keep dividing the constant by 10 eventually we will reach one so we divide one by 10 here to get the new value and I think that my comparison to make sure it's in range has failed so if that's correct then setting one will also fail no that works fine does 10 work I did save that I did build that so 10 has produced the wrong result so have these but the wrong result is correctly shifted left yeah this is a division bug so let's create a test procedure 15 equals 10 j in 15 equals 10 okay in 15 equals I divided by j and sub test you know how I said earlier that we had managed to get all the bits we needed that doesn't work I appear to have been slightly over optimistic F34 so low we set up I this is the value we are dividing by which goes into L we now mask off right remember how I said that I didn't that I had failed to understand complement arithmetic correctly so I was expecting this mask to take the absolute value but that's not actually what's going to happen now this should not be affecting this particular situation because all the values are positive so yeah it's still 10 now we do the subtraction the result is negative zero we we've just done the comparison and it thinks the result needs to be zero so the comparison has worked so we load zero into a zero we skipped the end so we end up stacking it unstacking it and writing it to the workspace which is our second variable so that has produced the right result no it's not 10 divided by 10 is in fact one not zero you'd think I would know things like this so CCS has got if it's if the value is negative positive zero zero positive zero negative zero so we're going to need some more labels positive zero zero label negative oh but this is not going to get the sign right is it oh this is awful this is going to yeah we need to be able to return either negative one or negative one or positive one can we get that from here no this is getting to be way too much code to actually fit in line so this is going to need to turn into a helper a helper routine that goes in the skeleton I was really hoping to avoid these but okay so what we're going to do is load the load the constant into L that's the thing we're dividing by sorry the thing we're dividing by load the thing we're dividing into a call our division helper and on return from this the result is going to be in A so we can store it and we're going to guarantee that no overflow happens division routine div how do we do this we need some temporary storage things into this is why I didn't want to take things out of line do we have a way do we have a nice way to do to get the absolute value of something I don't think we do okay create some temporary variables for the cached value of A and L we write that back here so exchange with div A no we're not going to do ts div A this has to use lxchange div L that will corrupt L we now need to compute the masked the absolute versions of both of them so we can get the so we can get the absolute values in order to do the actual comparison so this will load div A and branch on it we have positive positive positive zero negative negative zero so these can be no ops so at this point we want to extend at this point we want to load the value in div A and negate it by subtracting it from zero we do this zero subtract div A that might overflow so set it to div magnitude A and then we want to do the same thing for the other one so if div A is positive then we get the sign of l div L oh this is awful there must be a much better way to do this so this actually divides the A L register pair provided we make sure that the thing we're dividing by is always going to be smaller than the thing we're dividing we should be golden with that we can see I would like to just be able to put the thing we're dividing the dividend in the high word but this produces a single precision output how can we avoid needing to do sign calculations well we can calculate the magnitude of the two variables that will tell me if it's going to fail or not if it does fail can we do something particularly evil by synthesizing a value for the dividend which is bigger than the divisor but will divide into the same result are we going to have to alternatively we could just like say that this doesn't work and bodge around it for now but this is not right so what this is going to do is we're going to end up with what so this gives us that is the absolute A and absolute L so now we can load absolute A subtract absolute L do yet another comparison based on A so a positive result means A is greater than L so it's safe to do the division A zero result means the two the divisor and the dividend are equal this means the result has to be zero equal so the simplest one is div zero where we simply load zero into A and return the next simplest is div safe where we load we load zero into A we can't do that just yet we load we use L exchange to load the the low word of the dividend the right way around the dividend into L we then load zero into A and actually do the division and I've got that wrong we're actually doing A over L so the thing you want to divide here is A but it goes into L because the divide routine uses an AL register pair so here we do the division by A div A this gives us a result in A so we just stop right div equal is the case where the sign where the magnitude of the absolute value of and the dividend are the same so the result is going to be either plus one or minus one and this is actually simpler than I thought what we do is we load div A we subtract div L so we're going to see if the values with the signs are the same or not if they are the same then the same signs so that we must be returning plus one otherwise we return negative one so we need some constants C plus one is decimal one C negative one is decimal minus one so C negative one C positive one return yikes okay that did a thing what was the thing it did and run really run why is that not running that just breaks where are we? that's weird right I believe that what's happened is something oh I mentioned before that Yule's error handling is a bit not so great right CCS is not an extend BZF is an extend these are constants okay this is wrong but these appear to be right so just double check our program that should be a 10 so we're in div what have we got computing A over L right so A and L are both 10 so we're going to store this in div A and div L div A is 10 div L is 10 we're now going to take the absolute value so A is positive L is positive we're here we're now going to subtract ah we didn't set all I mean the code is getting increasingly horrible and big well that has actually worked less well so that should have we write A to A and absolute A and absolute L okay that looks better that looks a lot better I don't know if it's right but I think that's good enough to keep going with our game so let's chop these out and put our code back in still not right at least we got 1000 in our fuel is showed as correct our altitude is not our velocity is quite wrong okay oh yeah let's check the thrust thrust looks good I mean this is actually producing like reasonable results this one still scares me I think there may be some kind of memory corruption or something what have we got altitude is being displayed wrong but if we use a constant we actually get the right value this suggests that something is corrupting the content of altitude so we're going to have to go back to the debugger did you know I actually thought this would be a fairly quick thing I'd completely misjudged the weirdness and complexity of this thing so what have we got here we are reading altitude and calling diskey set register if 30 I do not see a reference to altitude to be honest so what is this doing load a constant stack it load a constant stack it C13 is C29 that's just C13 is 1 oh I think I got to build it so now let's look at a code one going to take right now it is actually reading altitude I know what's happened I do know what's happened it is memory corruption so the first time through okay first time through this is going to be D80 O which is not what I would expect let me put that into decimal D80 O is I5296 right what I think is happening is diskey buffer here is being overrun it shouldn't be I think this is overwriting altitude which is next so this is the main subroutine workspace we have written to the diskey when we cleared the yeah that D80O is wrong we have written to the diskey when we call diskey clear so it initialized the diskey buffer here and we have what look like valid opcode values then we've got 3e8 so I'm pretty sure it's a thousand so we're loading W1 plus 13s that is 0, 1, 2, 3, 4, 8, 12, 13 yeah okay we are actually loading 0, 3, 8 so where did we get that D804 this is just 1 plus 13 altitude octal everywhere octal it's always octal these offsets are octal right that's a decimal constant that's an octal constant that's a decimal constant that's an octal constant octal octal octal octal so much octal see that thing about octal is it to the eye it's indistinguishable from decimal at least with hex you've got like a decent chance that it's got invalid decimal characters in it this is going to be a long video okay let's try that and run diskey and run right so what was happening is once the offset got above 7 it was just reading garbage out of the wrong bits of memory and we just happened to luck out that none of our constants contained the digits 8 or 9 otherwise you would I hope have produced an error right okay okay this is worth a commit but I've actually been doing a lot more commits now let's go back to our game let's put all this stuff back again pulled run turn off the debugger for now and what do we get go I seem to have just crashed yes we are descending quite rapidly and we are below sea level I might want to scale that up by 10 this here I might need another scaling I might need some more scaling if we assume that we are never going to be above 2000 meters scaling factor given that we have 14 bits of precision that is 16384 so if we have a scaling factor of we don't actually have enough so we are going to have to bodge this we actually want our scaling factor 10 will do we are not actually going to add an extra factor of 10 on to sub-velocity here the idea is that velocity and sub-velocity together will be a sort of double precision thing we are still decelerating pretty quickly also if sub-velocity overflows then we are in a world of pain so we need to deal with this and we are actually going to have another depth there so if sub-velocity is bigger than 100 then shift a bit of precision into velocity likewise if sub-velocity is less than 100 then then shift a bit of precision into I say bit shift some precision into the main variable so what are we doing now we are slowly accelerating upwards why are we accelerating upwards interesting that caused by this code oh hang on we don't want that at all and in fact we want this to be a while loop so once the sub-velocity overflows our critical factor we keep shunting chunks of velocity into the main variable okay that's working and after a while we should see this wait a minute why we should be seeing some velocity there a little bit let's just take that off yeah okay what our numeric range is actually too small we need those double precision numbers but that would require reworking a lot of the code generator and I'm not going to do that just absolutely not I mean at some point yes but not now but this is actually like behaving so let us apply some let's apply a bit of thrust so we are descending push the joystick forward we are gaining fuel which is a bit weird what's the thrust and our velocity is not changing which is what I would be expecting that's because I forgot to add that on right the reason why yeah the reason why we were gaining fuel is that when you push the stick forward thrust is actually negative but we weren't seeing that happen because there is no minus sign up here so we are going to simply do thrust equals minus thrust just to get into the right way round and we don't want to allow negative thrust so if thrust is less than zero thrust equals zero so we are slowly descending joystick forwards we are losing lots of fuel our velocity is increasing we are ascending ascending more rapidly and we run out of fuel no more thrust we are in a ballistic trajectory we want to use less fuel so let's give you more fuel there and scale it we also want to apply a bit more thrust so we are descending stick forwards I think we want a bit more gravity let's try changing that to 5 because the player has no way to move down the only thing they can do is thrust backwards so and there is actually a pretty long delay between yeah that is a lot of height I wonder if I just want to start landing at like 100 meters that would give me a lot more space for precision and we have a we are now hovering and slowly descending again I think I need a bit less thrust the engine is quite powerful and yeah let's start at say 200 meters forward we also have a lot of fuel so this is not going to be a particularly difficult game I know how to make this more interesting so we started a thousand as before except our velocity is going to start heading downwards at quite a clip but possibly a bit faster than that so the player is actually going to have to decelerate pretty sharply faster than that yes this is more like it and I think we may have just okay we are now going up so we now have to sort of wait for gravity to bring us down again yeah and we've used 250 units of fuel so let's just give the player 3000 oh that's a thrust too much thrust that's reasonably tricky and we've landed we hit the ground at a safe speed which was to whatever these are so that looks like a game alright awesome oh yeah we don't want that testing here we need a bit of game state to go around this so there is actually one more diskey thing we need to do which is a key this is going to wait for a key press the way we do this is we read from input channel 15 and it gives you a ski code I believe what you get is zero unless something happens so we do loop asm extend asm read 15 the capital letters asm copy into i if it's not zero then break what we're going to be using this for is both to it's both so that the program will start without the player actually falling and also once we've detected crashes we will allow them to restart the game let's see if this works key press not found pause for key and we don't actually care what the key pressed is ok normally I hammer keys to make the diskey respond but of course that may ok I believe that works we started out at a thousand and we haven't crashed yet so ok now the actual game outer loop is reset game altitude equals thousand by ten velocity equals 100 sub velocity equals zero fuel equals 3000 so the actual gameplay is going to be infinite loop clear the screen reset the game run for one game tick the purpose of this is to initialize the diskey with the game view pause for a key press then keep going and while altitude is positive just keep going around the actual simulation loop once the altitude stopped being positive we then test to see whether the player has landed let's have that as an arbitrary thing if the velocity is sufficiently low then just display a marker values from the time being then we wait for a key press and go round again so we are descending in freefall at 100 meters per second and we hit the ground and and we do in fact immediately restart so either the key press was buffered yeah the key press is not buffered what's happened is that it's not returning zero if there's no key press so what the documentation here says is that there needs to be an interrupt interrupt mechanism incurs a Kylie within YA AGC okay we need to more skeleton stuff so we've got two keyboard interrupts interrupt five causing the software to examine the channel that's my screen recorder we're nearly there we nearly there this is the last thing to do so one two three four five key rupt one I think all we need is this this will diminish the value of key whenever the user presses a key so in order to wait for a key press we set it to one and then wait for a zero okay yeah it is key rupt one we wanted so pause for key is CA zero zero transfer to storage we can't overflow because it's zero and so I store it to key that's wrong actually we want it to be a one I store it to key load key store it to I yep while I is not equal to zero loop let's try that and also we need to just I noticed the theme didn't work the way I thought it did so we're just going to move this stuff into display game state so that we reset game display game state one game tick display game state game state and let's give that a try 118 syntax error that's a cowl goal error yeah that was complaining because the N sub didn't match the loop one of the reasons why I picked this syntax is after working with Ada for a bit I realized that requiring scope terminators to the key word of the scope opening is a really good way to avoid tricky to spot bugs and readability issues okay press the key and it's ending wait for us to crash okay 9999 means that 9999 means that we have actually landed because a very big negative velocity means yeah that should be greater than minus 30 but now we should be able to press the key again reset the game proceed okay start this ending and we crashed reset game proceed okay that works quite nicely we want some special effects so when you've landed we want to let's just make altitude zero velocity zero turn the engines off automatically display game state so that will just show a nice landing display with no embarrassing underground spacecraft what we're going to do when we crash we are going to wonder if this will work loop state this one this should give a flashing display on blank does this work disk is connected we're descending yeah kind of more than I wanted so let's just do that say 10 times sending okay that looks okay for a crash press the key to proceed go again let's see if I can land it this time that thrust okay I crashed it this feels like a game okay you can't use proceed you have to use a number key interesting so actually use most of our fuel decelerating from our entry burn so maybe I just don't have enough fuel to land now get that down below safe value I think we can do it I may want to make the landing velocity a bit safer and we've landed it's a game awesome right I actually do want to just do one special effect we have some because we can actually set the lights it does look like they think that bit one is what most people call bit zero so display game state zero if velocity is less than minus 30 which is our critical value lights equals lights for three 100 if if altitude is less than 100 then 100 this is so we're actually going to be using set raw opcode for this light zero velocity minus 30 then 010 010 5 bits highlights 0 if altitude is less than 100 then highlights equals I know I'm just getting myself confused they will both actually fit in the lower they will both fit in the lower digit space this is register 12 nothing, nothing, lights let's see if that works the address is not in reusable memory the required extend is missing I bet this is that's trying to load a constant with CAE to show you fixed apparently we haven't used or with a constant to forget the required extend is missing extend ok and run it why is our altitude ok, there's an altitude warning but we're not actually starting at the right altitude what changed have we managed to corrupt our altitude with this I think we have the velocity light should be on but it's not so that's yet another code generation bug what's going on here load our constant ok, this displays the fuel value which is here now we go on to dealing with the lights so load a constant which is probably 0 stick it in lights load velocity compare it with a constant subtract it yeah positive, zero, negative ok that looks fine is it just a simple buffer overrun I think it might have been actually and also I think I'm lighting the wrong lights the low bit is VEL and 16383 is the wrong altitude so lots of thrust in and get our velocity down does the light go out yes it does also my velocity my critical factor is way too big because I've forgotten that we weren't displaying the scaled value see, we landed altitude warning is lit yes, something is corrupting altitude but if I take this out, everything is fine that's not right let's try taking this out ok this is something about writing to the diskey segment 12 is making things go horribly wrong 12, eh yeah, that's all complete garbage so what's wrong with altitude 12 this is where we actually call diskey set raw opcode so altitude is plus 16 let's just do that because that will stick the octal prefix on all our 00, yeah luckily yule doesn't care so we're just lit with that because I have a feeling that we may have more base issues diskey set raw opcode this worries me I don't think that's the bug but it's certainly a bug this should be can I get I'm sure that there's a way to persuade Prince F to put the prefix on OK, yeah, yeah we can I can do hash 0 for that so yes, that's done the right thing percent 00 turning it into percent percent hash 0 that didn't do what I wanted it to do 0 percent 0 into percent hash 0 that's better sensible, what's this that doesn't look right that does not look right at all no, that's a 0, that's fine decimals those are 0s the amount of workspace for the main subroutine is now 24 octal words let's see if that's actually made a difference to the program running no, it hasn't you know what this is fixed if it wasn't for this issue Pro does not seem to trigger interrupt I wonder why set raw rock code that one digits display game state, this one I want to stick a breakpoint at x59 very good x59 run so altitude is currently w1 plus 15 so that's w1 so this is the state of our disk address 12 that's decimal contains the light so that's 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 oh no, sorry this one, this one the bottom most word is not used and contains garbage so reset that means 2710 here is our altitude 2710 to decimal is indeed 10,000 okay so we we're now at disk set raw rock code we load our parameters subroutine1 workspace is undisturbed subroutine2 workspace has got register sign upper digit lower digit we have loaded a with subroutine1 workspace that's here that is, wait, why have we done that c3 oh yeah, that's the address of the digits storage array which is this c is our register so we add on so we now have the address of 8, 9 which is this, that's correct so we dereference it which gives us eOO which is 6,000 which is 0110 e is 1110 it has extended the sign bit into the overflow bit, that's fine so that has read the right result 2710 is undisturbed so we now go through and we we've stored the old data we look at the sign bit yep we skip doing anything with the sign bit yeah, I don't think there's anything particularly useful here so let's just skip all the way down to where we do our we're looking for the right 10 which is this and there aren't any useful okay, we just have to skip through this there aren't any useful labels we can jump to you see and I haven't figured out how to actually go to line numbers or anything wait a minute, are we here no we're not, we're still further up at some point we reach a right okay, we've done the right let's just double check our workspace 2710 is still undisturbed we're now going to write back to the uh the array so c3 should be c3 is the same address of our array 899 is the same address as our array so we should be updating this value here and that value should have changed, it is now 604 and this is undisturbed yeah, and nothing has happened altitude is still exactly as it was calling diskey the set role keyboard here has actually made no difference at all to the value of altitude so something else going wrong time to just do that okay, start game no comment out, what's going on this is really weird I don't understand what's happening okay, that plays that plays that does not, okay let's try setting it to a different thing is it just address 12 that causes the issues we call it set register and that calls about one okay, I'm actually I have had a thought about what might be happening so let's take a look at the symbol table shall we ha, right this is what's going on we have, our program is too big we have overrun our ROM bank and we've spilled into bank 3 so if I take this out and reassemble we still use a few words from bank 3 to figure out the memory map no, actually I think we've got far I think we're fine for memory in fact we cannot be running out of code because our main program, the bulk of our main program in the game loop is at the bottom of the program so if we were running out of space that wouldn't that wouldn't work at all but it does look like this called diskey set ROB code is causing the is it triggering the issues okay well the amount of data we've got is actually not terribly significant W1 here is the biggest piece so let's just copy I'm just going to try and diff the two together this is going to be a complete mess because all my label numbering is different but let's see if we can get anything at all let's actually do that right so this is bad code compile display game state and down the bottom of it we can see it called diskey set ROB code so let's copy this into 2 now we comment out this line recompile and at the bottom of okay so that's no longer there and now we compare the two together right so this is the the only difference in code between the two files I was expecting more I was expecting this to allocate a label but it hasn't are we taking too long do we need to reduce our frame rate so what's it actually doing nothing it's not running at all so I did see this before when the source code was garbled the debugger produces these messages when interrupts happen so this looks like an interrupt happened and then something has gone wrong of course it would be nice if we had xip printip it would be nice if we could tell what the registers if I could make it tell me what the what the program counter is I'm not sure what this is telling me I think this is showing me the log of the the labels it's hit show threads no this only implements a subset of the available commands so info interrupt interrupts are allowed last ISR is 3 ah info registers we're at 0 why are we at 0 there's a stack this does look like a stack it looks like this is just remembering all the labels it's passed through so we've been through x22 then we went to x24 this isn't a real label remember then we jumped div so this looks like div0 is going to x24 which is a re-entrant loop that's bad so div0 should be returning have we corrupted q? well that gives us a place to start let's try break div0 run can't ok that looks wrong break div0 run what's in q that looks like an address ok we are at code line 291 which is after the div which is just where we expected to be so we store the result in s11 now we loop back and we're x22 again yeah this is what we so div0 x24 x22 x24 again and then div so the next time we hit div ok we're here ccsdivapos should be put us here that's died so what's wrong with this divapos have we hit divapos before this smells like another simulator bug ok can't that trace this is not data now this is not code I think I think we have hit banking issues and our code has spilled into multiple banks except that that bank has not actually been loaded so the top address we can get at in octal is 800 common fix exists within this area fixed fix exists within this area so I want one followed by four zeros which is one address we're at looks fine symbol obfo is assemble obf8 yes ok so our program stops at ocoo there isn't anything else there so we're working from this document it said that this particular memory area was directly accessible and requires no manipulation but there's also a bit elsewhere that there are overlaps here we go common fixed bank appears at fixed fixed address four thousand while common fixed bank three appears at six thousand six thousand so six thousand in hex yes is coo um I think the initial fix is to do that so that should that should advance to bank three which will then appear at the appropriate address let's try and run it well let's try to look at the code bfc no that's complete garbage I need to do set lock six thousand that gonna work that is also complete garbage both the agc banking system is kind of weird common bank appears at fixed fixed address six thousand we are did I tell it bank three I did tell it bank three oh wait a minute wait a minute banks and blocks are different we want block three not bank three the documentation is inconsistent so block three it also doesn't help that there are several different models of agc called block one block two etc so they this is pretty heavily overloaded and there's still garbage there fantastic our program is just too big what can we do to make it smaller we can make some better code here we go comparing equal we actually we can replace all this stuff with this is that enough to make a difference I don't believe it is yeah that's div zero we are we're here we need to scrounge together this much we make this program smaller somehow we can remove features so this only ever have this only happened when I tried to make the lights flash so but lights flashing is a nice feature I want lights to flash we can take these returns out that will save a little not quite enough we are up to div plus four no wait I think we can that's there then there's return decimal one that just fits so yep that works velocity warning throttle so now we are descending very slowly picking up speed and ballistic trajectory and the velocity warning light comes on we're going too fast dangerously high descent speed if we hit the ground at this speed we will crash and I'm not going to bother actually waiting for that to complete so I'm just going to run that again as you can tell I haven't figured out how the banking works it's complicated we don't have a particularly big program but we're using one complete bank yeah okay let us descend just in free fall towards the ground rapidly altitude light and then we crashed okay can we continue yes we can I think that's finished let's just copy test.cow lunarlander.cow add it our game occupies every single available word of fixed bank to this one everything from 4000 octal to 6000 octal don't know how to get stuff into block 3 but yeah anyway we don't need it admittedly there is no space to put anything else in so I believe that what we now have is the only action game ever written for the Apollo guidance computer for about 40 years I believe this is the only high level language compiler for the Apollo guidance computer and I think that this is possibly the only piece of non trivial code written for the Apollo guidance computer for about 40 years so that is other than test programs used to verify the uh verify the CPU on things like the Apollo guidance computer restoration project although I believe they mostly relied on the self tests inside colossus and luminary themselves so I have a feeling that this video is something like 12 hours long we'll see but I need to sign off now so that I can rapidly edit it render it and upload it to YouTube before Saturday expires I hope you enjoyed watching this video even though it took your week please let me know what you think in the comments oh and if you happen to have a spare LEM sitting in a basement or you happen to recover the one in deep space or anything like that please try my game and let me know what you think thank you very much links in the comments