 It's time to do some coding So people have been following the space news recently may have noticed that the Hubble Space Telescope is having a spot of bother One of its computers is failing The Hubble Space Telescope has been upgraded multiple times by shuttle missions But unfortunately this particular computer has not been upgraded. It is the NSSC-1, the NASA Standard Space Flight Computer one and it's based on a 1968 ish architecture and It seems to be going wrong and unfortunately There is no way to repair it if if they can't fix it via software means so That may be it for the Hubble Space Telescope Now I thought it might be interesting To try and do a assembler and simulator for the NSSC-1 unfortunately very little is known about it however it does appear to be based on a another computer called the OAOC from 1972 which in turn Is based on the O803 and the O803 instruction set is available So I can't actually Implement the NSSC-1, but I can implement the O803 so I am going to do that Here is the document with all the information in it, which you can see here is dated November 1968 It is surprisingly small 82 pages and a lot of that is documenting the assembler. It's got what 30 odd instructions and another orthogonal instruction set with 18-bit words This assembler is supposed to assemble from punch tape or cards Somewhere here it actually lists the number of instructions Which I cannot find Anyway, I am not going to duplicate this assembler because it's really weird Here's an example of some of the source code it actually accepts and yes, this looks like English text Let x times x yield x squared let y times y plus x squared transformed by square root yield norm If norm is not greater than 1 then go to compute inside the unit circle otherwise etc etc the instructions have two forms the three letter mnemonics and Extended English descriptions. Here's the list of instructions. So Let turns into the LDA instruction and so on let location of is a load effective address People have been trying for years to do natural language programming in English and it always turns out to be a terrible terrible idea so I'm going to implement the the machine language form only and Because I'm doing this in a hurry. It's going to be bolted on to my existing cow goal assembler Architecture, I've got here the 6303 assembler, which is really simple So it basically kind of be much the same The simulator itself is going to be in C because I want that to be more useful to other people Here we go The architecture has 50 instructions 30 of which require a memory access the other 20 of which Are simple instructions that don't take a parameter. There are only two instruction forms So this is one that takes a memory address so we have a 12-bit operand field a 5-bit opcode field and one bit to indicate whether the The value in the operand gets subscripted through the Indirection register It's got a single accumulator, which is 18 bits wide There is another accumulator that's used for double width Operations, but it's not really a general purpose accumulator Subscript register, which is used for indirecting pointers a Scale register, which is used to do fixed point arithmetic and a few other minor things and that's basically it so First thing to do is let's just copy the copy and paste the entire 60 a3 assembler and that assembles there and Let's just drip out some of the 6303 stuff Let's actually change this to 0803 assembler 2021 There are no register symbols in use in the instruction set the Register is always going to be part of the opcode itself It's the register parameter is implicit So we don't need any of them All instructions are very similar There's actually only two forms. There's going to be the simple form and the noun form noun in the terminology used by this document is Referring to a memory location. However, it doesn't quite work the way you might think so for things like LDA that Loads the value at noun however So the the value at that Address gets loaded. However, if we go and look at for example, where's a branch instruction? Here we go This is part of this is a conditional a branch instruction If the content of the decision register, which is a single bit that is the result of the last comparison Is one then the content of the storage at the effective address is placed in the instruction counter So this doesn't jump to noun. It jumps to the value held at Now noun is a variable containing an address and Even things like simple go-to work like that, which is going to make programming a little bit weird by today's standards. I believe that this assembler Would automatically allocate a word of memory for use to contain a addressing for control flow. I have yet to spot any instructions that Actually work with the address of noun other than LDL load location Which loads the effect loads the address of noun into the accumulator register a Here you can see the instruction format Which is 18 bits wide which is six three bits octal Nibbles, I think you call them So the whole thing is supposed to be operated in octal And The only Addressing mode is whether the subscription bit is set this bit here Now I would like to find out what the syntax is. Oh Yeah, XED to indicate that The value is supposed to be subscripted. I Don't like that. So I'm writing the assembler so I can make up my own syntax. I'm just going to use I think square brackets So that matches the way other assemblers work You're not going to be able to assemble traditional OEO 3 source code assuming any survived with this assembler Okay, so let's just stick in here. These are the opcode handlers for the Uh Six three three so let's chop out all of these except simple Simple is the case where no parameters are taken Because the OEO 3 is an 18-bit machine We can't write out the result in bytes very easily So We're going to have to write each instruction as a 32-bit word with 14 of the bits wasted, but that's fine. Not a real problem. Oh Yes, and I also don't want I want this Convert to ext to dear that's another three thing. I don't want that Don't want that Read operand this is This is the code that reads a six three three value which can either be a register a immediate number or An Undirected thing so we're going to have to replace this So values that we can read are going to be Let me actually make a test file. Oh This is the test file. I did for my last assembler if you don't need any more Okay, so let's add two numbers the way you're supposed to do it is value a I think add You go value be ST a result using the English syntax that would probably be Let value a plus value be yield result now value a is going to be a These are going to be constants in memory and the result is Going to be just a word to put the result into So that we can see that there's now two types of value. There's immediate which are these and There is addresses So value a value be in result are all going to assemble into addresses pointing at these so If we're going to want to do an indirection that is you want to read from the table Let me see how this works Indexing is specified by using either suscripted or XED between the verb and the mnemonic and the noun on memory location LDA XED number will result in load accumulate destruction So that the subscript register will be added to the address field at execution time to form the effective address So number in this case is actually going to be a table like so so to use that you're going to load the The indirection register, which is X with With you go LD X Loads the subscript register this will load X with to We then load a Using this the indirection syntax Which will add to To table So zero one two that will produce six and then we're going to add on four and the result ten will be written here Okay, so that will work With this syntax it is going to be possible to do something like this, which is not going to do what you want it's not going to load four into the InterX it's going to load whatever's at address for which is not necessarily what you want a Very simple extension to the assembler would be to add constant pool support. So if you did this then the assembler would Allocate a address to put the four in and substitute that address here in the instruction That would make coding much much simpler. I'm not going to do that for now alright so so the only case we care about is Is this is the token we're reading a square bracket if so it's going to be an Indirection So let's just stop that out for the time being otherwise, it's a Expression We do need to be able to tell the the opcode handler routine whether it's a address Or an indirect address don't have addressing modes anymore So Dressing mode equals That's just a simple address Actually, it's cool. It's a value. We don't need any of that. Okay. What happens when we try and compile this? Right, we haven't done our omit 32 Actually 18 bits will fit nicely into 24 Waste the top six bits of the high byte, but That's fine So gotta write this out little endian so 24 omit 16 is part of the Framework code Yeah, the friend my framework unfortunately is written to expect that words are 16 bits We really want to get rid of omit 8 as well This is this architecture can only address words. It can't address bytes so Yeah, I was slightly too clever for my own good when I factored all this stuff out Well, the mid 16 has only used in one place which is to find word We do need to implement it other it otherwise it won't work So for the time being I'm going to pretend that 16 bit values Well, I'm going to pretend that 18 bit values are 16 bits wide So I'm going to write out my instructions with omit 16. This is going to need some proper work later to fix it a bit and Of course, I can't do that Okay, I'm just going to define my own routine for omit 24 like so Yep, but I still need to implement omit 16 Which is just going to produce a This architecture just going to produce a error, you know, why doesn't I touch this code? That should be in pool Okay, right. So our assembler, which is this compiles But it won't work Let me so these are going to want to be Longs Yeah, we really want these to be w for word, but Let's go with long for the time being So it should be able to run my assembler and it fails on line one because I haven't actually Added any instructions yet All right, so we're going to start with the simple instructions They're called simple because they don't take any parameters. It's just a single word which writes out a single Value to the output stream, so How are they going to work? Let's start from the top sta lde st e add Mul div Yes hardware multiplication and divide in a 1968 architecture This thing is noticeably more sophisticated than the Apollo guidance computer It's amazing how quickly the state of the art was progressing given that this is dated 1968 and The AGC flew in all the Apollo missions Of course Apollo 11 was 1969 So the AGC was in use after this machine was designed Right ADC is the first one That adds the carry bit to the accumulator 00006 neg negate There should be a number here interesting Let's see if that appears anywhere else in the document. No, it doesn't there's a There are some program listings down here I'm going to look to see if there's a either a summary document or Can I rotate this? Yes, I can looking for a summary document or else a Disassembly with a negate instruction in it Yeah, this is more source code so defining for external use factorial defines a exported label if it is less than zero then go to Rackets return from factorial So if it the accumulator is less than zero then return If it is equal to zero then go to answer equals one Answer equals one is a label to find down here And so on This looks great. I mean you read it and you think you understand it The problem is that it's still a machine language and If you phrase one of these sentences in a way that still makes perfectly good english sense But isn't phrased exactly the way the very simple minded assembler wanted it Then your program doesn't work So here is the program split broken up into instructions Is there a negated it's a minus one Don't think there is Oh, it doesn't do. I have a proper dump This looks like a program dump negation No negation I believe this is the address and these three bytes Here are not the opcode The the listing is defined elsewhere in the document. So if I find one of these things I'll go and look for it Alt tail yield let Not using negate Someone must use negate Right, possibly we're stuffed It may be possible to determine what negate should be from Looking to see which instructions aren't used Okay, well, let's go back to our instruction list. Ignore that for the time being Okay neg negate That takes a these will take verb cmp complimented is 00010 00010 cycle double cycle normalize Normalize is interesting. You give it a integer. It keeps shifting the integer left Until it looks like a Until the leftmost bit Well, you can look at the instructions here. Look at the description here Uh, it's shifted left until the 17th and 18th bit 18th bits of the accumulator are different so, uh The And extended accumulator So what this will do is it will keep shifting a 36 bit number until It represents a fraction That is a fixed point number between zero and one And it will also keep track in the scale register of how far it shifted it So this was very useful for doing anything with Fractional values and floating point which I don't intend to Okay ldd Close extension with decision shifts left one And copies the value of the decision register Into the bottom bit reverses the order of the bits, which is 22 uh, the o o prefix here indicates octal ldx stx adx brook brooberum And an oar now these don't and an oar values what they do Is set to the and or register Which is used for chaining together multiple comparisons Because I'm not sure actually Uh, when you are assembling conditional operations You know if if x equals one and y equals two the way we do it these days is we use trees of conditionals Uh, the way this architecture is designed to do designed to do it Is you execute all the Uh, the all the actual comparisons Uh, and you use and an oar to indicate whether the do indicate how the comparisons are compounded together So it doesn't do any short circuiting of unused expressions, which makes for slower code, but easier to understand I think And possibly smaller five ilt iet i gt Uh, these do a Uh, these do a comparison, but rather than doing a branch they update the decision register Okay, if overflow tov over o1 If parity odd i op this o5 Is positive i gz Is o3 Is equal to zero i E z is two one Uh, take sub verb cpd on seven Not is o2 Okay, let's put these in numerical order See if anything Comes to mind I don't see a seven 10 11 o12 13 14 15 17 21 i gz and i op really Yeah, five and o3 Yeah, I Can't really make anything of that It's possible this architecture is microcoded in which case the the arrangement of the instructions won't mean anything But uh, I still don't see a negate Uh, nop is the No, no not the last instruction. I thought it was Never mind So halt is zero stops and waits for a external interrupt ssa set the scale register. That's the one that's used for Uh fixed point operations Set page ldp Uh allows Uh so there's 12 bits of That's interesting Three. Oh, yeah. Yeah. Okay 18 bit instructions But address parameters go in the bottom four nibbles Which in octal is 12 bits. So the page register Is used to indicate which page of memory you're referring to with those four digits Rob reset the overflow is seven red reset the decision bit is 23 Exit fires a particular interrupt Interesting. I've read on I've read the interrupt section. I forget what interrupt 16 is supposed to do This might be the system call instruction But honestly, I think this architecture is a bit too simple for system calls Okay, tin i o which is doing for all i o operations. That is the end, right? So what do we got? 123 missing four five six seven 10 11 12 13 14 15 16 17 20 21 22 23 I am going to make an executive decision that neg Is four because there's an obvious hole there And it looks like they've been allocating instructions from zero up So single hole single instruction that we don't know what it is Right Now let's go back to the top And let's do the other ones So lda load the accumulator two zero One two three four Up noun LDL load the effective address is four zero STA store the accumulator LDE Load the e-register This is the the extended accumulator that is five two ste store the e-register Is one zero ad is oh four mul is four four div is six four It would be really cool at some point to port cowl goal to this architecture That won't be today Uh It's quite small it can it can only access i think it's four killer words So you're not going to be running the compiler in one of these machines. It is a true Von Neumann architecture So the instructions are read from the same address space as the data Uh, there's a really interesting instruction that reads a value from a variable And then treats the value as an instruction and executes it I'm not sure what that's supposed to be doing but Uh I've done that one This is possibly our four ETR This is the one that actually adds values. That's three zero three zero Murg this is or is 50 E or which is actually surprisingly E or Is 70 done that one The shift instruction which shifts in both directions Is one four Shifts in one direction if positive in the other direction if negative a positive is left Uh double shift is three six Which shifts both the accumulator and the e-register together just looking at the comparative speeds Yeah shift and double shift both take exactly the same amount of time even though they're doing Double shift is doing twice the work Uh three six Cycle which is rotate is three four Double cycle is five six. What do you guess what that does? Normalize ldd flip ldx is five four stx is seven four Uh step subscript which lets you add to the x-register directly Is o two Okay, these are the branch instructions So brook is four two which is unconditional branch No, brook is conditional branch brew is unconditional branch which is six two broom is Content and instruction counter plus one is stored at the effective address The content of one location greater than oh, right This is the jump to subroutine Uh instruction Now this machine has no stack This means that saving the return address Is difficult you can't just push the return address onto the stack Instead what you do is Uh the return address Is stored At the address Give and hang on Okay, I need to try and get my head around the various levels in directions So the content instruction counter plus one is the return address. It is stored at noun okay, so The thing you actually give it Is a variable in memory The content of one location greater than the effective address is placed into instruction counter, right So you call broom You give it Uh as the effective address the location before the subroutine And the return address is written into That location So to return from the subroutine you use brew here also called return from Which loads the return address? And jumps there So you have one level of Subroutine calls you can't do anything recursively This actually suits my cal goal language right down to the ground. This is exactly The kind of architecture that cal goal was designed for Okay, uh, had we done broom broom is oh six And or if less than is two six is here two six Uh is equal to is four six is greater than is six six. There's obviously a pattern here Done done done done Uh x n g t if subscript is not greater than If the content is the subscript register is less than or equal to the content of the storage at the address Then do the test stuff Okay, this is This goes with adx This allows you to write loops using where the loop counter is the x register You use adx to increment it and x n g t Which is two two to test for the end of the loop Uh done done done execute. This is the one that executes an instruction one two Not sure what this would be useful for but There you go Uh set scale register is three two lds the s register ssa you've done ldp is one two That's not Ah, right wrong one. Also, that's ex u not ex e tin resume from Right this is this returns from an interrupt. It loads all the registers from a Uh memory block. This is seven two i o is the all-purpose i o register Actually, what it does it depends on the value at noun It is one six Uh the this architecture It's got standard programmed i o that is reading and writing from ports But it also Basically says that you'll hardly ever use this. There's another form of i o which it calls cycle stealing Which these days would call dma Where the i o unit actually writes directly to memory And it wants you to use that instead and that's the end. Okay so It's in the top two digits o o means it's a simple instruction We haven't seen an o2 Yes, we have It's here Let's do that properly two four six. I haven't seen an o8 Of course, I haven't is octal two four six 10 12 14 16 20 22 Uh i l t two six Okay, that's correct. I haven't seen a two four three zero three two three four three six four oh four two four four i et is a four six five oh five four five six no five two six oh two four i bet i g t is six six seven oh seven two seven four what does l d e supposed to be That was at the top l d e is five two so that goes here uh that goes Got that wrong again i l t i et is four six That's in the right place two four six two four six two four two four six two four six oh two there's no two Four that's a quick skim three six one four seven oh five zero three zero zero zero six four four four oh four zero Not seen a two four two four two two two Yeah, no two four That would be interesting to know what that actually did And you also need one more Which is our dl Okay All right, we haven't done up now So let's do this one first We've read the token We now want to read the expression We now want to expect a close square bracket Set the Addressing mode to be a m in that should simple enough Okay Up now and see the simple call back uh right if The addressing mode Is am in then we want to set the Uh the indirection bit in the address is 13th bit which is and the Token value. Yeah, we want to then write out the opcode or with the token value that says token number okay, and our token number is a un 16 Which is Right, and we haven't done op dlcb. We can steal that from here The lcb. We keep reading expressions This assembler assumes 16 bit values So the result of an expression is actually going to be too big to fit Right, this is actually going to require more refactoring So let's just Avoid that for now Okay duplicate symbol during init your Oh Blast Right now this is because the Uh The framework actually defines its own Hang on does it See, I thought this was because the framework actually defined its own symbols for Called things like your But it's been a while since I've touched this code. I'm not sure that's true So where is it setting the default symbol table? I'm going to look for dbcb There's a bit missing Stood sims right Yeah, here we go These are the standard symbols that are always defined And this is conflicting with the symbol defined by the Architecture now. I think this came up with the six through three I'll one of the other architectures Right the 8080 doesn't use the framework it in fact Defines all the symbols itself Uh, this actually has a complete implementation of the assembler in it. I just haven't got round to refactoring this out to use the framework What else is there the pdp 11? This uses the framework So I believe the pdp 11 one Doesn't use the standard symbols No, it doesn't Oh, yeah, yeah, it's this include line here that does it? Yeah, I'm an idiot. Okay. We can easily work around this by simply Taking the standard symbols And cut and pasting them into Our architecture File like this This will also allow us to do things like remove db We want our own ds Uh, we're going to keep dw for the time being Um, and we can change this to x or right And does it work duplicate symbol during Uh, okay The symbol lookup is not context sensitive. So, uh, these are operators Um So i'm either going to have to rename this Or rename the instruction I'm going to rename the instructions and d and or d for And an or decision bit your is a your a Don't like doing that Okay, now what's it complaining about? Error at line one syntax error. Okay. It's trying to Read a Expression No, it's not I don't think it's getting that far. It's at least getting to here. Yes. It is Ha, uh, okay So what's going on there is that it's hit this expect line and is saying I thought you were supposed to be at the end of the line But you're not you're in fact Uh, the the lexer is sitting there Because I completely forgot to read the actual parameter So let me remind myself how that works They expect operand right line two syntax error um, I am going to guess that this is Something to do with the square brackets and I yep, this token So Things like square brackets Right, everything else is single character token. Yeah, um the That is this so Right, what's happened here is that read expression actually returned the terminating token So what we want to do is if token is not equal to This then Let's just see how the framework's doing it its expression So if read expression is not a closing square bracket then Terminate to index now and it still doesn't work. Why doesn't it not why doesn't it work? Okay, it hasn't actually It hasn't got that far Okay, that's because I actually need Because the read expression here returned the next token which we're consuming We actually need to call read token again Because read operand is supposed to return the terminating token So it doesn't work. Okay, it's it's correctly read the Right, we don't want that Because I expect operand has done it for us Right line six under vine instruction Yeah, put these back to w's I need to refactor Yep, I need to refactor the framework again to allow the use of 32 bit words I mean it's easily done. They just need to replace some things with type depths But it's annoying And this is just going to do omit 24 word as you in 32 Okay, and it worked What is it done? Let's write out a listing file And this is what we get Right now this is in hex and it's little endian So We have 18 bit words That does not look right Okay, let's find ldx Here we go ldx In octal is 5 4 So Let me What's a good way to convert from octal to hex? No, you can't do that in lua You can't do that in lua you can't do that in julia That works Okay, so what we're expecting is by 4 2 3 4 Is a value that looks like o o c o o 2 so o o c o o c that's actually correct that seems so Okay, o c is the address of the uh Of value a which is this o c in fact the addresses are all wrong Because each instruction is a single Word the program counter actually only goes up by one each time So we'll have to fix that in a moment So o c is actually correct for the address c o is correct for the Um The next Byte of the instruction this is c o These two digits here Uh, however, we would expect the high bite to be o 2 And it's not So I reckon that symbol here Is defined by the framework To be an int 16 it is symbol defined Here it is value u int 16 Okay, we're going to have to start fixing this to a certain extent It's the simplest way to do it Uh problem is that I'm going to have to start refactoring a few things Okay, why isn't So the framework file is the first thing that gets loaded No, it's not the framework is loaded second good. That's easier So What we can do Yeah So this include kawasem.co actually defines the framework So we can simply do a Word Uh to be You would like to do that but find a type Type def even I've forgotten how my own language works is Okay, uh, and we can go into here and simply change this to word And lots of things fail Because we actually need to change a lot of these things to words 101 is operator callback Operator callback takes Word Word 9 Program counter Is Pointed to a word program counters are also words the number Is a word in fact i'm going to just look for you in 16s that one Yes, that one and that one that one that one and that one in text 16 current value You in 16 this will produce the wrong value In both cases If if it's a If the value of word is bigger than 16 bits, but let's just go with that for now Dean Okay, right word is a partial type. This is actually compiling one of the other assemblers Because we do need to tell these That word is a uint 16 3 p11 And it compiles That was suspiciously easy. So what does this do now? That's better Okay, so that looks like the right instruction Right now let's fix the The omit stuff So let's actually hang on a second hang on a second Now i've just refactored Uh So that the framework knows what a word is i can just change dw In the framework So token number is now a word Okay, and that fails because i've changed the name of omit 16. So this is now omit word Uh, this is now omit word This is omit word And this is omit word Okay, why what is looking for omit 16? In the framework these days Oh, yeah, I know what's happened Uh, this is now an omit word these omit 24s also become Omit words because words are the only things we can omit arch pdp 11 6 303 tlcs 90 Okay, that works But we still need to fix our program counter stuff so omit 8 is it omits a byte and increments the program counter So what we need to do is find the definition of omit 8 which is nominally complicated and What this does is it deals with multiple pass stuff, but the core bit is this So all we're going to do is to Hack it Uh, so We omit three bytes program counter gets omitted by three then we adjust Correspondingly what does this do? That's better So value a is at address four Table is at address seven Value b is at address five and result is at address six Good and let's just stick a hold in for good measure and that looks correct So I believe we have a working assembler so Come in at this and let's start work on the simulator So I am going to take a short break to refill my tea and do the boilerplate setup Be back in one second All right. Here we are So I've set up some boilerplate and the build tools so that when we do a build it does actually produce a binary Which is called o a o 3 mu which is rather harder to type than you might think Which is currently as empty as a c binary gets Now i'm going to copy a lot of the framework from the cpm emulator so Let's just cut and paste all this into Here There's no command line fatal is helpful o a o 3 mu entity bugger startup There are no arguments, which is helpful The only thing you can do is load a binary Option parsing Entity bugger Nothing there So command line is So Binary file name is so if this is null or Opted and plus one is not null then Syntax error So here we want to parse the options Initial initialize the emulator And then just keep stepping the emulator Okay, and that won't build Because I'm having to do the std pool.h That's better This should be a star Hang on i'm doing that wrong like that Okay, and I haven't done the emulator tools yet. So H Emulator in it Extern void emulator run Okay So we now go to the emulator file and I will just steal these And we can get started. Okay. This is the bit. This is the interesting bit This is where we actually get to start playing with the architecture of this thing so 18 bit words and four Four killer words of memory I believe So I'm trying to find the actual the overall machine description that tells me Big things are it does not actually say well Okay, we're going to assume Uh A full 18 bit word address space. So I'm actually going to do const Static const int memory size is This number of words therefore This is going to be our memory Uh uint 32 is even though we're storing 18 bit ints. It's just easier that way Okay, emulator In it nothing emulator run They're fatal Too much cow gold variably mod of oh You do have to do our hash to find Okay, so we now have a binary an emulator with stuff in it Uh, it doesn't work because we haven't actually loaded an image yet Which we need to do here Just thinking do I need to pass in a parameter for the address at which it gets loaded? I think we're going to assume they all get loaded at zero so Okay We get a stir error from there. No, don't you know string dutch Now we wrote this out in the assembler as a three byte Words, so we're going to need to read it in as three byte words So So read in a byte As a uint 32 Okay, so Our Test file is this that we just loaded in these numbers don't look like the Don't look like the numbers I saw on the hextam. That's better. That looks more like it So we should be able to do away oh three test image And it then loaded it And then failed at this point. Good That is actually what we want now At this point we could actually start doing the The machine state, but I'm not going to because I am going to go over here to the cpm emulator And I'm going to steal nearly all of this Because it's a debugger and I'm going to use the debugger Uh In this code Some of it's going to need to be modified Because you know the the oeos 3 doesn't have BDoS etc And a lot of these reads and writes will need to be different This is all set up to actually Uh, you don't need any of that This is all set up for, uh, lib z 80 EX that emulator But it should be easy to modify And we had forgotten there needs to be a dissembler here this Stuff in emulator in it needs to be Well, some of this needs to be moved into here and flag enter debugger Yes, we do want this Flag enter debugger is actually set here So it's a bool we need to put that in here debugger Yeah, of course that doesn't build and the just time being Let's just comment out nearly all of this Okay This goes away And this is going to be the actual main loop of our simulator So show regs We don't have any registers yet. Obviously It's not like the the register state of this machine is complicated Oh and read line see how I That into the read line Okay, right that builds So if we run this with minus D to enter the debugger It instantly hangs Oh, yeah So what we want to do here is Let's start defining some state Okay So this thing has a very limited number of registers Which is nice for us so A register I'm just going to call out the register storage limit is Uh A very simple simple memory protection Which we're going to have to implement subscript register that's for doing indexing into tables write the scale register s c The page register and then these are bits carry decision or and Overflow So we want to print these put that there for reference So the way there's the z80 state is displayed is that the Uh The flags are first so we'll copy that There's four flags Oh, I completely forgot the most important one of them all The program counter Okay, so a equals You see I like hex but this architecture likes octal Uh Let's do hex the problem is that 18 bits doesn't fit into X very well Let's use octal. I don't like octal, but So e equals oh six oh s l s s is oh So carry Uh, yeah, let's go for or and and overflow Just so that these are all one character. It makes life easier c d a v c d a v followed by reg a reg e reg s l reg s s followed by Just how much space do we have? six 12 24 Yeah, we can put the other two registers here So the scale is six digits Which is two Scale is yes, that's Two binary uh two two octal digits and page is Also two Okay, now we haven't printed the program counter because that's supposed to go here in the disassembly stage But i'm just going to ignore that for the time being and just do last f for flags and we do need to include We've lined up each Okay, right now down to the main loop address of course is 32 bits what's value Now values for watch points So that also needs to be a 32 So if if the program counter is on a breakpoint then break Check the value of all the watch points Oh has changed from 6 0 to 6 0 If single stepping into the debugger If tracing display the registers Otherwise we're actually going to Simulate in instruction Okay, so what's that? Right this drops us into our debugger Four flags are unset all our registers are set to zero that all looks fine And in fact, let's just squeeze the program counter in this loads of space It's nice to have the entire machine state On one line There you go Program counter is 1 6 6. Why you said 1 6 6? Doesn't mean initialized to anything Because that's supposed to be there that's better All right So we're now in our debugger We want to i mean the first Instruct the first debugger instruction. We want to Implement his memory, which should be straightforward That's this one You give it two parameters and it dumps the memory between those Of course, we want our parameters to be in octal We want them to be rounded to eight bit. No Uh Many Octal words Do we want to display can we get eight in I think we So that's nine by six, which is 54 out of 80. Yes, we can So we want to display eight octal words per line Okay, let's play the address like that This ascii dumper is also the dis dumper hex dumper is also supposed to display the ascii representation but uh This machine would never have used ascii So I think we're just going to ignore that Okay So we should tell how our memory dumper. All right dump from zero to 20 Not quite what I wanted Okay, let's try that 20 okay now We just look at that listing so These don't look anything like these So these values Are clearly these ones two four zero Hello, but these See this one shows a little bit Right what's happened is I've completely screwed up my loader. This is why I wanted to do the memory Thing I think I need to do this to make sure the sign extension doesn't happen No, that wasn't it So what do we have here? 05 coo 2 5 coo 2 0 8 1 0 0 1. Yeah, okay the The memory dump in the listing does actually match what's in the file But these are kind of garbage So we are actually getting incorrect data But why The precedents of the shifts are weird. So that wasn't it Just checking that to be sure ah I'm thinking in hex So if you shift by two digits in hex, that's eight bits if you shift by two digits in octal That's six bits. I mean it's still wrong Yes, um, that's because I'm completely mangling my octal my hex. This is a hex dump. This is an octal dump Uh It would have actually been better if this were an octal dump, but that would be annoying to implement. So I won't do it Um, I really want to compare Yeah, let's just go for Instead of dump that in Hex idiot idiot it says Shouldn't be doing this quite so late. Okay And we'll do some what how does that look? 0 5 c 0 0 2. Yep 0 8 1 0 0 1. Yep 0 6 4 0 0. Okay, right. We are now loading the binary correctly finally So let's just get rid of that tracing Run it again m 0 to 20 And this is the actual octal instruction set and you see these actually look like well uh 5 4 was ld x which is Here Plus the address which is 5 Which is this. Yep That is fine. We've now correctly loaded the binary And we are Dumping memory the breakpoint stuff should just work minor tweaks. I just need to change these This is Listing the breakpoints. This is setting a breakpoint Watch points. This is a unit 32 watch points current value is 6 0 Delete the breakpoints by address Oh, yeah, I need to update these The octal Delete a watch point like that Okay, so now we should have Fully functioning breakpoints. Let's set a breakpoint to address 3 List it back. There is a breakpoint at 3 set a watch point of 4 list it back 0 1 2 3 4 current value is indeed 0 set watch point at 3 Yep, that's correct. Okay, so I think breakpoints and watch points should work Uh, oh, yeah, we don't need the bead off stuff Tracing should should work Um, well, that's just going to be zero or one. So it doesn't really matter what that is Disassembler Do really want one. I suppose you better do the disassembler So this is actually going to be This wants to call uh Z80 x DASM to actually do the disassembly because this is based on a Of course, this is the Z80 debugger But let's change that to very simple disassemble Into the buffer Address all instructions at the same length. So we don't need to care about Len In show regs here. We actually want to T states We want to disassemble the current instruction in order to be able to print it Okay, so now the disassembler proper Buffer The address okay, well so this masks off the Uh, the top four bits of the opcode four bits five bits four bits five bits these five bits seven Six, yes, that's correct no yes Yes, okay, the top six bits of the opcode if the Five bits even uh, if it's zero then it's going to be one of the simple instructions Otherwise, it's going to be one of these instructions here so go to our Assembler and we're just going to copy this table here. Now. I'm trying to remember my Vim Column operations will this work? Yes, it does good And of course C doesn't do that syntax for octal Instead it uses the thoroughly confusing and dangerous zero prefix Okay so, oh and we do also want the Default for Unencodable instructions All right, and now we do the other ones these ones This is going to look pretty similar, but of course these all take a Parameter and we don't want to return after these but we do want to break and of course there's the default case Okay So if once we get down the bottom we've printed the uh The opcode we now need the argument so first We want to check for indirectness which is going to be instant and See these they start bits at one rather than zero which is really annoying So it's 12 So if this bit is set then You see now i'm trying to remember how to concatenate using sn printf it's Doing strings in c is a really annoying um, there uh Is there a libbsd function that does it? I don't think there is E printf New extensions that were standardized Uh, will the these will allocate memory? Uh, can we do this more cleverly? Yes, we can So rather than actually call printf there, we're just going to set the format string Okay, that'll do it. So this is then going to become buffer buffer size format Uh So if we're indirect that wants to be a open square bracket Then we want the instruction We want the four bottom uh digits of the instruction that's these 12 bits Followed by the closed square bracket And I believe that's all there is What's this complaining about wrong octal syntax? Okay, and this is correctly disassembled the instruction at address zero So let's go ahead and do the I've done unassemble So I should just be able to say zero nought twenty These need to be a zero nought twenty And we disassemble our program Does our disassembly match what we see here? Uh ldx five Ah, yes. Yes, uh Lda 10 Of course, this is hex and this is octal add Six is this one Store in seven. Okay. We have our disassembler right Now the reason why I wanted to do that is because the decode for the disassembler is exactly the same as the decode for the Simulator so now we can start work on that bit and what we're going to do is simply Copy this Actually, we're going to put this in a different function For reasons that will become apparent later all right so First thing we need to do is to Move all this stuff and replace it with This Here as well like so Okay, in fact, we can be slightly clever about that. I wish I thought about this before I did it We have to subtract one from the program counter there Because we've added one here good We are by the way starting execution in the wrong place. It shouldn't be at zero It should be starting from the the execution starting interrupt, but we'll ignore that for now Right, so our ldx was five four. Wasn't it I think I've scrolled past it five four ldx so this is Here we go Right the content the storage of the effective address Is placed in the subscript register Registers altered subscript register. So we're going to be doing a lot of fetching Effective addresses So we're going to do a function for that Which returns an address now it's going to be instruction so We want the bottom four digits if The indirection bit is set It's not an indirection bit. I'm thinking of a different architecture. This is the subscript bit so now We add on the Subscript register and that becomes our effective address so five four is very simply Not that Not that it's uh We are then dereferencing that and we're going to define a couple of functions for doing this because particularly when it comes to writing We actually want to Do some extra work Ah, it's not called x. It's called s s Wait a minute Yeah, we're going to call it x because that makes sense and ss doesn't because like the Instructions their instructions call it x Okay, so we run that So we should be able to step once And we have in fact loaded the value of two into the x register. That's good So the next instruction is going to be lda Which is the same thing but for the accumulator So naively we would expect this to be A equals read ea instant Break however We have A sum of these other bits now when do these get set? So Harry stores the carry out of bit 17 of the parallel adder So this is only used on add instructions. Uh, the other interesting one is overflow Which won't happen on an lda Oh, and of course There is no parity instruction A parity bit Parity is calculated by an instruction Oh, that's nice This means that I don't have to update any of these bits based on the result of ordinary operations Only special operations Have to do this Also, I've completely forgotten about the page bit So Other page flag rather So this is actually going to be Reg p shifted left by 12 Ord with the address Okay, so this should work And this is also going to test the indirection So load x with two Now we try to load a And we get six is six the right value Yes, it is table plus two is 10 10 decimal And that stores the value six And then the add fail Okay, that's good So let's go through and Uh, implement some of the other simpler ones So 4 0 is ld l So this is actually Actually, I'm doing that the wrong way around We don't want to do that. We don't want to add on reg p here Reg p is actually Should be calculated by the effective address calculator. So all this does is deal with a real address That simplifies life here. I can just do that right st a Is six zero So we're going to write reg a to The effective address now right is interesting Because There's protected storage Now, how does protected storage work? This is the s sl register Storage limit controls where writing into memory is permitted It'd be nice if there was more information about that. Okay, let's take a look at the instructions controls no no Storage limit registers page 63 Okay, as an 8-bit storage limit register which is used to enable blocks of memory into which writing is enabled because in flight you don't want to accidentally overwrite your Attitude control code because that would be bad The register is broken into two nine bit fields a and b Where a is b b 1 to b 9 and b is b 10 to b 18 Oh These b is a bit that could be That could be clearer But they are going to the going to a nice effort to actually define absolutely everything I have to say this document is a joy to work with it is so concise and clear And some of the concepts have got odd names because it was 1968 But a and b represent upper and lower limits on the nine high order bits of a 16-bit effective address Between which writing will be permitted if c is b 8 of b 16 of the effective Address ah, yes Uh I've given it too much memory. You can't I think there's only 16 address bits An effective operand address for one of the four instructors above Then if Yeah, right will not be permitted If a equals b then 128 work block is enabled Okay Right now do you need to rename the register? What do the instructions call it? Set scales set page One thing this could do is do with is a concise table of all the op codes But they're clearly pushing the english language Syntax so much that I don't think it would fit Okay, that's interesting. I mean there must be an instruction that does it if comparisons and Call Return Jump step subscript Save subscript Flip cycle shift compliment your Interesting, uh, is it actually documented storage limit? Content, yeah, I have a horrible feeling. It's not defined here Unless you there isn't an instruction to set it and you have to use the interrupt code Which will save the Storage limit That's Quite plausible So on startup The state of all the registers including the storage limit register will be read from the startup interrupt block So maybe that's where it's come from and there is no way to change it on the fly Other than to update one of the interrupt blocks Yeah, I think that's quite likely okay, well We're going to set it on startup For now So the storage limit needs to be So a wants to be all ones and b wants to be all zeros a is for is the right most They are two Okay, so that's 369 Okay, so a is This field is all ones b is this field which is all zeros, right So now we need to implement our I need to implement this so a is going to be b is going to be Now these represent the nine high order bits of a 16-bit effective address So we actually want to shift left by shift this left by five and this right by b8 to b16 That's oh, that's inclusive. That's nine bits With their odd numbering. So three nine right shift by four So the b ends up aligned high okay, so b address a If b is less than equal to c and c is less than equal to a then Perform the right. Haven't done add yet. Okay, so we're not actually going to get that far Let's do add Add is Of course, this means that you can only write to the bottom 64k words of memory But I think that is fine It doesn't give the overall specs of the machine You get 12 bits of address available Yeah, here we go memory size as large as 64 64k words requires a 4-bit page register should be loaded and stored in program control So it's added to the 12-bit address here to form a 16-bit address Okay, so the memory size is actually going to be one shifted left 16 And this also means that our read and our write Need to be if address is greater than memory size Actually, I believe the system is likely to just ignore the extra bits So let's just do This instead All right. So where's that add instruction? Here we go. Oh for So this is actually a little trickier than it looks because we need to set the carry bit so Um, let's just put that in a helper function like so So the simple thing to do is simply to do I'm playing it out like this because we're actually going to use the same code for adx So the obvious thing is to just do that however, that doesn't set the The overflow bit now the overflow I'll carry and Overflow when does overflow happen? Is that multiplication divide? Yes overflow register continually Oh overflow does happen with ad if carry occurs Oh, that's nice. This machine can distinguish between carry and overflow which is really interesting All right, so Now how do we actually compute this? This is actually a known trick with emulators and I am going to go look it up Okay, I'll admit that the difference between carry and overflow Does make my head hurt but I think I've got a reasonable handle of how it works. Let's define a few things So This is going to be the largest signed word we can represent Which is 17 bits So that is Three six Yeah, okay, just this That's the This is going to be the smallest Value we can represent this is negative value So that is going to be Which is going to be that Now it helps no end on this architecture In that we can represent we can actually do the Arithmetic in 32 bits so we don't have to So we can detect carry and overflow after we've done the addition rather than before There is actually another thing I need to do which is this This is going to sign extend the word So the way we're going to do this is we're going to shift Our word left Until it until the sign bit of the word aligns with the sign bit for 32 bit integer So you want to shift it left 32 minus 18 bits And then we're going to cast it to a signed value and shift it back again resulting in Resulting in a appropriately signed number and I'm actually going to put that as a helper function here Just to make the code easier. All right So Left hand of our addition is signed interpretation of value The right hand is the signed interpretation of what's at that effective address We compute the result and we are going to return the result Masked down to the appropriate size Right. What are we going to do about the? Sign and overflow bits. Well, they both start out as zero so like so If the result is less than The minimum value we can represent Or the result is greater than The largest version we can represent Then we set the overflow bit You can do that with this actually Now the carry flag This will get set Whenever the Whenever a carry ripples off the left hand side of the value Which will happen I believe during every overflow But also when the value passes zero So that if you start with two and you subtract four It doesn't overflow But you do get a carry propagation so How do we do this because Uh It's not sufficient just to say has the left hand side changed sign because if the left hand side Is minus Two and you add four that will cause that will change the sign if I'm just wondering if it's symmetrical So my initial example was two minus four Well two plus minus four Let's try that the other way around. So we've got minus four Plus two That will produce the same result but won't carry Okay, let's go with the left hand side changing sign. I believe that's right like this down So two plus minus four carry gets set Minus four plus two Carry does not get set That seems plausible Yeah, let's go with that. I'll have to check up on all this later. It'd be nice if there was a test suite I mean, there probably is a NASA somewhere, but I don't have it In fact, I think it's I think there's a reference in the documentation to it either somewhere under diagnostics 24 Diagnostics a set of cpu diagnostics is this which is aimed at testing the execution of all machine instructions Yeah, I would love to get my hands on that Okay, but we're going to have to do this the hard way. Okay the sign bit is in bit 17 by my numbering so if value and 117 Is not equal. Sorry if left 117 is not equal to result and 117 And I can do better than that. I can do like so. Okay, and The overflow bit is actually called fv All right, let's see what this does So load x with value of five load with that So we are now Going to be adding on the value at six which is Four So six plus four should equal decimal ten. Let me double check that A is six The actual value six We're adding on the value at six which is this which is four So six plus four is Decimal ten or octal one two Does not look right Anyway, let's try the store store into seven Here's our a b and c for the bounds checking. It looks like it's in bounds, but let's actually look to see whether it did it Uh Zero one two three four five six seven it should have gone there and it hasn't Okay So if b is less than a dress True and a dress is less than a True a sign and a dress is Oh, a dress is one one. Sorry. I'm looking in the wrong place. Yeah, this is the value here It's being put in the in the Wrong place Uh This is one one Yeah Sta seven, I know what it's done I know what it's done It's read what's at seven And is using the content Of that Yeah, the content of the cumulative is stored at the effective address Not at the address stored at the effective address so sda That is what I wrote. So That's interesting So what ea going to do when given with a Seven Well, this bit's not set It's going to war in reg p, but p is zero It's just going to return seven. So why are we getting 11 for address Okay, we're going to need to Use the debugger Okay step step step step The address does look wrong Instruction is right. Is this going to work? Yes, it is going to work Well, that's wrong Okay, so let's try that again step step step, right Reg ea Continue Step. Yes, I am using a debugger to debug a debugger one so Noun is seven Uh, yeah, yeah That's this is wrong this These should be like that That's probably what was affecting the ad as well There there we go a is now one two, which is correct storage address seven Let's look and see what that is address seven is this which is one two, which is the right number We've just correctly simulated our first program Good, right. Let's add some more instructions Just going to go from the top. So lda Ldl sta Lde is five two. It's this one All right. E equals I think complicated there ste Is one zero add we've done Okay, multiply and divide. I'm going to ignore for now adc plus carry Is This one Okay, so this adds on the The value of the carry flag to The a register. I'm going to change my mind about this You left and you right So this now just does the arithmetic. So this is going to be I'm going to need to put a read there Because this will then allow us to use the We want to add reg a to reg fc done neg negated is Uh negating all zeros use values zero Use the carry register one right, so this is actually subtracting from zero I didn't spot a sub There may not be one Well, this is the one that we decided was four Okay, so Okay, this is actually special. It's not doing a conventional Subtract so we can special case all the All the things so negating all zeros Use the result of zero and set the carry register to one But doesn't touch the overflow Negating all zeros use the result of zero Negating the number that has zero is an orbit positions except the sign Which is going to be this Use the same number as a result And sets both the carry register and the overflow register to one otherwise The carry register is reset to zero And the overflow register is not touched All right, and with Three zero This is simple Yep Okay, that looks like so don't have to care about any flags Five zero for or Seven zero for e or which of course we renamed cmp complemented is this one All right now this is value by Amount Low six order bits of the content of storage at the effective address So We are actually Going to need two lower six order bits But now the sign extension is going to have to be different Okay, so that should do it Actually, we don't need the and anymore So if the count is negative Shift right, that's easy if So hang on if it's shift if it's positive it's shifted left with zeros otherwise yeah I'm looking to see whether it's a Assigned or an unsigned shift it is a signed shift. So if it is If it's less than zero Shift right otherwise Left okay Double shift this shift a and e together So Three six down So this is actually It's a bit annoying. So we're just going to have to Hardwire the register values okay now To 18 bit to values Let me just see which way around it goes the extended accumulator is to the right of the accumulator And its sign bit is not shifted Wow That's weird Positive shifted right if the count is negative Yeah, okay, right that's a bit weird. Okay So To the the low order 17 bits Our reg e The high order our reg a Sign extended like that. So if amount is less than zero Shift value by minus shift value right by minus amount else Shift value left by amount so reg e mask of Just so it's just the sign bit left Then or in like that except we're actually wrong here We want to shift left by 31 because we're not including the sign bit Reg a becomes becomes the high 18 bits But we also want to set the Uh sign bit if you want to set the overflow flag I just realized I forgot to do this for the other shift So we're actually going to do this Then So Reg a equals New reg a okay Okay, it's it's easier to do the sign bit here because our new value is just value so If value and sign bit Is not equal to There you can get the old value from here new value and sign bit Value okay Really want that test suite cycled by oh chord So this is rotate and doing rotates and sees a bind So it's essentially the same code And i'm going to do it with hard wired registers because that's actually easier Two's complement shift counter the content is negative the content shifted cyclically right Bits leaving the low order position entering the sign position. Yeah The count is positive Right, this is unsigned that makes life so much easier. Well, there's no such thing as a signed rotate Okay So if the amount is less than zero we are shifting Right yeah so reg a becomes reg a shifted Right by minus amount or with reg a shifted left by 32 minus amount i believe It's okay. We're shifting right by one So amount is minus one So reg a shift right by one now The bottom bit now needs to go to the top so We actually want to shift left by 17 So given that amount is minus one That should do it in that direction so in the other direction We're shifting left by one So we want to shift the top bit down to the bottom So that is 18 minus amount i believe and no flag set good. Okay Double cycle count is negative the content of the cumulative incentive accumulated cyclically right Leaving the low order blah blah blah cumulative entering the sign of the End of the accumulator. Okay, so this is shifting this the extended accumulator just sign bit Right that looks straightforward You can do it like this so Extended accumulator goes in the low bit reg a goes in the high bit. Oh, yeah, and we also want to To make sure it's unsigned So we are shifting That's actually 64 I get that right for double shift. It's called. Yep so 36 plus amount and 36 minus amount value value so reg a becomes the high bits reg e becomes the low bits Right, and I actually want to test this one So let's go to our assembly file So, uh, how are we going to do this a is the high so that's going to be one Value e is going to be the low which is going to be zero so uh LDA value a lde Value e and we want to shift right by ones. That's minus one This will work double shift by amount And let's leave it at that So let's assemble it Check it looks plausible It does and run the simulator Okay A is one E is zero We're going to shift right by one so expect this one to show up here of five six. I forgot to put the instruction in to psi Read the a incident Let's try that again Yes, that worked Our one moved from here to here Uh, let now we want to shift in the other direction. It turns out we have a one in value a This is where constant pools are handy Shift right Shift left Didn't work that'll be why and I'm actually going to rename some of these to be the Opcode name just for clarity And it's back the way it was a is one and e is zero good that bit works Okay What do we got next Normalize Okay, this is actually fairly straightforward. We just keep shifting left The sign bit extended accumulators not shifted is one of those We keep shifting left And the scale register gets Change we start Yeah, we set the scale register to zero If the content of the accumulator in positions one through 17 of the extended accumulator is zero Then the scale register is set to zero. Okay That's not bad That is this one so we want to assemble a value from Like this We don't want it to be but we don't care about it being Sign extended because we're shifting left, which is easy Also, my douche here is wrong. This wants to be a 17 not a 31 We want to reset reg sc to zero so while Now the until the 17 to 18 bits of the accumulator. These are the top two bits of the accumulator Are the top two bits of our combined value, which is 18 plus 17 bits wide Which is 35 so We want to check to see whether bits 35 and 34 Are different i.e 01 Or 1 0. Let's do this the slow But sensible way Yeah, not exactly rocket science. Well, actually this is rocket science Given that one of these is in the Hubble space telescope, right? So now we've done this we want to break down we want to copy value into reg a and reg e again so That's basically this code left gist count is Greater than or equal to the width of the type but the type isn't in 64 Okay ldd close extension with decision Content of the accumulator extended accumulator shifted left assigned the extended accumulator is not shifted in the vacated low water position The extended accumulator is filled with content of the decision register. Not sure what this one's for I know what this one's for It's for long division This is an operation that you do a lot in long divisions Um, i'm gonna guess that div instruction Here we go. Uh, no, this does. In fact, this is a two-word divide the right content of the sign feels positioned in the vacated Left overflow is impossible Yeah, I Toe really need that uh Test suite So this is a two-word divide, but I So why would we be doing long divisions as well? Hmm Okay, so we assemble our value Shift it left by one or in the decision bit and Put things back the way they were 22 uh I have a vague memory. There's a standard function for doing this Last order of bytes, but I don't see anything about bits Okay, well, it's easy enough to implement. Um, it doesn't change set any bit so we can do it the simple way So clear reg zero Um, that's not right Want to copy the sign bit to reg a So we start with a one in value So that We're going to or in first 17 zeros Shifting right each time Okay on the 18th cycle through Our one's been promoted to the sign bit and therefore it gets copied into Reg A but I don't think we want to do the shift after Let's test that one. Okay. Yep. That does it See our one has moved all the way to the left. That doesn't mean it's like Guarantee to work, but it's at least sanity checked it Uh, okay, ldx. I think we've done that one. We have st x 7 4 adx is ad Does not change Anything other than the subscript register so Okay Do So it doesn't change it doesn't set any of the other carry or overflow So that's actually straightforward. We can just do like this Okay, one of these Yeah, it's getting quite late And i'm clearly not going to finish this in one session So I think I am going to call it a day for now I mean, I'm going to splice these together Uh, but I'll also take up the opportunity of the break in to Do some hunting around for that test suite because This is so badly going to need one I may have to do a cal-gold port to the architecture just so I can verify that the emulator Works reasonably well All right then Well since yesterday, I have done a little bit more research And I have discovered two interesting things One is that this machine is not actually called the o a o 3 That is the name of the spacecraft it was in This one the orbiting astronomical observatory mark 3 The name of the computer is in fact the On-board processor or obp it not a very Imaginative name. I assumed this was a description rather than the name The other thing I found is documentation for this The advanced on-board processor or a op This is the The next generation of computer after the obp and looking at the design. There's a lot of stuff here on the Uh actual assembly the bit. I'm interested in is right at the end, which is the instruction set It's basically the same machine with a few more op codes This is very useful as it Fills in some of the blanks For example, it actually describes here The structure of the interrupt blocks which we'll need to actually implement interrupts and you know start the machine Uh, it also shows Uh a couple of instructions So you remember that I had to guess that the negate instruction neg Had the opcode o4. Well The aop has o4. So I think that is correct Uh The other one is that it's got a Or is it this is actually an alphabetical order? We go a subtract instruction at opcode 2 4 And if I go over here to the assembler Remember, I was surprised there was a gap in the instruction set There was no instruction at 2 4 So I am going to guess that There is actually supposed to be a sub instruction here It's just uh, the document I was looking at was missing it. So I'm going to put that in and then implement it So we now have sub and neg in hopefully the right kind of places It seems very peculiar that there would be no sub instruction, but there would be a neg so uh The aop looks like it back was compatible with the obp Um, I haven't actually verified that for real yet. They've renamed some of the instructions as well Uh, like here for example Not there What am I looking for? Test accumulator z i a z t z I think this document might not be searchable Yeah Uh, these are all ancient ancient scanned documents. So it depends whether whoever scanned them pushed them through acr or not Uh, the instruction I'm looking for here is uh Test if accumulator is zero Which I think and this one is i a z I think that's a minor opcode No, I cannot find it Well, uh on this one it's uh On this one it's ie zed on the other one on the aop it is ta zed um I also noticed that this uh document doesn't mention the english language syntax So, uh, they obviously reworked the mnemonics But i'm going to stick with the document I currently have for now Another thing that came up is that i'm really going to have to write a test suite. I couldn't find one I'm sure the nasa one still exists somewhere on a printout or a tape in the bottom of an office drawer But i'm actually going to have to do one And uh, I'm also going to do a few modifications to The syntax now that I understand better how it worked So this was the Uh, the test file I was using last time. There's actually nothing at all interesting there. So let's just go to the Uh P emu test suite which is currently empty So last time I came up with This syntax meaning Uh load a with the variable at noun Store a into the variable at noun This uses this is This reads the effective address This writes the effective address Uh, I also came up with this Meaning Read effective address Plus x Now I don't think they make an awful lot of sense So what i'm going to actually do is change the syntax so this Means read or write effective address This Is the subscripted form See now it makes it explicit that we are adding x that should be a comma We're adding x onto the address of noun and then dereferencing the result There are a few instructions like ldl which just do this That is they are loading a with the value noun This also means that dw Is consistent because uh, this will take the value of noun that is the address And just omit it directly And uh, there's I think another instruction that takes an effective address It means there'll be a lot of square brackets, but it should be so much More consistent Uh, this also allows such things as you can indirect a pointer with Load the value of the pointer into x This adds x to zero and dereferences the result I mean you could do it with the old syntax as well. It's just it would be it wouldn't make any sense So Let's go to our Sembler Uh, this also allows immediate which is going to be important because I'm going to need to add support for that So Oh, yeah, we'll also need a register symbol Of which there is one which is x sub op rig Callback implements Symbol callback is Okay, so When it's a square bracket we read the expression If the terminating token is a comma Then we expect to get uh, Did I write an expect token? Did I write an expect symbol rather? I don't think I did. I tried trying to remember how I did this. I think six three three did it Oh, yeah, there's a read register What did that do? Oh, we already have a reg cb callback. So I don't need to implement this And this is just reg cb. Okay, so uh square bracket Then an expression And then we expect if there's a comma immediately afterwards we expect to register The addressing mode is therefore Uh Let's change that to noun Now let's change that to address and subscripted and I think that will do it be read Register okay So The other thing in fact there is no immediate. It's just a dress And but we are going to want to add it so okay Uh, so this should be a m address Is that going to assemble? It is subscripted Okay So let's build this It's o b c All's o b p mu test sweet Output to test image test list And what do we have in test list? These look like the same opcode. Oh Oh Yeah, I don't want to make the mistake I did last time these are hex not Octal So I think that is working We're also going to have to LDL does not take a index noun. It takes a Actually the So what I was thinking was that LDL is supposed to take a direct value Uh, however The description is that it computes the effective address And Loads the address loads the address of The Well loads the resulting address into a so it doesn't dereference it. It just loads it But it's still computing an effective address, which means you can still use the index form So we want to use the same syntax um, I Also, I hadn't noticed this instruction here load indirect I am looking at the right document. No, I'm not I'm looking at the advanced document Which has more instructions in it and in fact that was opcode one two, which is Okay, they've reassigned opcode one two from Execute to load indirect So that means it's not backwards compatible. That's irritating Okay, the effective address is placed in the accumulator Now, I don't believe there are any other instructions that Work directly on the effective address. Let me just skim these they all say The content of storage at the effective address The storage effective address Yes Yes Yes Content of the subscript register is stored at the effective address. That's just a right But it's still the content of the effective address. Yes Okay, the branch instructions were stopped last time The content of the storage at the effective address Okay content Yeah, brm Content uh content of storage content of storage content of storage content of storage content of storage content of storage Yes, yes Yes Yes, yes Okay, right. So they they are all in fact the same form, which is nice right So we've updated the assembler. We want to actually start doing a test suite now one of the issues with this architecture Is For example bru, which is go-to On a conventional architecture you'll be able to say bru label something like this However This syntax is not allowed by the instruction set. You have to do this and label here has to be a Variable containing the address that we want to jump to Which is really annoying so What i'm going to do is implement that stuff to do with constant pools So that you can actually write this And also this And what the assembler will do is it will Automatically allocate a label but allocate a variable containing the address Of Of the thing that you're referring to and in this case it will allocate an address referring to a five So that was actually what I wanted him for but that's not that's actually not needed So the way that's going to work that's a value No, hang on. Hang on. Hang on. Right. We have effective address subscripted effective address Or a value So square brackets mean that you're always dealing with a subscripted effective address or Whoops, I actually forgot to set this I see mode equals or an ordinary effective address. Otherwise, it's a value So this is going to be ea. Don't do anything to the opcode Subscripted effective address Yes, do something with the opcode And we're going to have to implement val And what val is going to do Is Allocate a label into the end of the program Write out a word of data And then insert the address of the label into the opcode As an ea now we actually have some support for this In the framework in these these segment things So Let me just remind myself how this works Okay, that does not look complicated So all we are going to do is here in value. We want to Take the current program counter in the Data segment advance it Only by one And we do actually want to define a label in the new Segment now that happens with the Uh With the not existing in that table would be Does this work again? So you want to add a Symbol Okay, here's here is the main loop of the assembler. Um, it needs to Set implicit label, I believe So this is Now this is defining a symbol. We don't want to define a symbol. This is an anonymous label. We just want to omit the word To The data segment Which is going to be Change the segment to the We don't want that at all change segment to the data segment We want to omit the token Okay, I don't think we want this anymore This token number should now be a word Now it's actually even easier than that I've always used the functionality that's there already Switch to the data segment omit the word Switch back to the code segment token number is the previous address of the Uh Data segment and we're also going to want to do Uh Because we're playing with the current segment. We also want to check for this Uh segment Okay, now this is actually pretty rough and ready Too many parameters in call to segment callback value What we should actually Do and why are we getting those errors? That's the z80 code complaining About Something we're doing our workspace is too big. Why is our workspace too big? Okay, uh, that's a calgo bug. We can't call segment cb here Ah, yeah, right segment cb is a symbol callback. You can't call a um You can't call a routine that implements a Interface from another routine which implements the interface. It would be good for the language to Actually detect that So we're just going to do this Clean things up a bit So here we can actually just do change segment segment data Change segment Second language text Okay Now I was saying this is pretty rough and ready because what we should be doing is keeping track of each constant we're emitting And reusing the values if you emit the same constant again But we're not doing that for now so So this should have actually done it However, it looks like it hasn't So what should have happened is that It read the immediate value here as a value token by this and then here it should have Done the stuff however It does not actually appear to Have done anything So this is uh The bottom 12 bits are zero zero two Which is this address which is not the address I wanted. So why Hasn't that Worked right hasn't that it hasn't hit this piece of code. That's why am I calling the right A routine. I think I'm not I have The build system actually requires a certain number of tool chains to be enabled which I disabled In order to try and do faster builds, but that seems not to have worked So we just wait for it to do a That's about half the system build. We still have disabled a lot of tool chains Shouldn't take long Anyway, while that's going let's have a look over here Let's do a Um, why did that fail to build right now to registers? odd uh Okay, what's happening here is that I've been a little bit too clever for my own good Trying to make the build faster. There we go. It works Works now Right. I was in fact running the wrong Yeah, the wrong binary. It's obc obp not obc. That's better So let's take out the print. I took out the thing I was wanting to test So bru label and label is Oh, oh, oh, which is the wrong value uh Um, I think I've missed a bit I actually think now I need to Do I need to tell it where to emit the segments? Where did I put the 633 linker? Don't think that's using segments Yes, I think that should have worked So, uh, that should have Here's the framework So in the main routine We what uh is this this assembler is really inefficient. We keep looping until no labels have changed And then we emit the Segments one after the other right I haven't told it that the data segment should go after the text segment I remember putting in some code to do that But I also have a bit of a feeling I may have taken it out again Because it's more useful to have the segments all exist and conceptually the same Address space but that's in the same numeric range for stuff like the 8080 where the Uh, you have your code segment and your data segment in separate address spaces So So this will set the program counter For the current segment. So I should be able to do dseg org oh I should be able to do that Can only use immediate values from the code segment So that has Worked I believe Let me try and turn this back into that's 3200 a 3200 in octal Well, you can see that it's omitted two values into the code segment So we have our two which is the address of label and we have a five which is the immediate here The five here you can see the address is uh It's got a one at the end which means it's the Second word it's red. So it's obviously getting the code segment Offset correct, but it has I'll blast uh, yeah, what's happened is the We've set the base of the code of the data segment After it's omitted the constant. So what we need to do is this That's better that has actually worked So the values here are the addresses of the data in the data segment However, that's not really what we want. What we want to do is to Tell it that we want the data segment to immediately follow the code segment so So this means that at the beginning of each pass Rather than initializing the These addresses to zero By default we want them to follow one after the other so Uh, how many segments do we have? text data and bss So what we actually want to do is to say bss follows data data follows text text is zero We do need to make sure that we initialize this to zero And this to zero Okay, the problem is this is a semantic change for the rest of the system So this may have broken the ms dos compiler If now if you want your Segments to start to address zero you have to explicitly put a org zero in but we should be able to simply Do this and that looks relatively plausible I don't see the The values being omitted that's a four which is after the end of The listing Well, here's our five And here is the The two which is the address of the label So the image is correct. It's just the listing that's not working. I bet that if I would put a deseg here Yeah, uh, it's this is a minor bug in the listing, but we're going to stick with it because this now works good so let's Commit this Okay, now our test suite So the first thing we're going to need to do is have the ability to actually write stuff out to the console And we're going to do that using the all-purpose io instruction See there are four different forms They all have the same encoding The only thing that's different is the io channel description, which is the thing pointed to by noun here Uh, there are four forms There's dma, which is this one. They call it cycle stealing here stealing Where's the other one the content of the storage? Put one two sixteen are a function code. Oh So this sets up a dma channel Content the communicator is placed at location seven, which is weird Content to location seven is then output to the iu unit as a starting address Right, so that will then write out a block of memory Or possibly either write to or read a block of memory using the same instruction This one just writes a simple value to A io channel Actually, it's right out of value to the io units as a whole So this performs an operation this writes the command from noun Whether it's one through sixteen indicate the data channel The content of the accumulator is stored at location seven and then outputs the io unit Right, so this one Outputs the accumulator to a device This one reads the accumulator from the device This one just does something And this one sets up a dma system. So what we're going to do is we're going to set up a Really simple implementation of this that Uses these two forms to read and write bytes of text so output to so let's load a capital a and One in bit 18 and a zero in bit 17 I think that will do it. I have the right syntax expected a single operand Okay, let's do this the easy way octal. This is going to be four zero two three four so Again, this has actually stored our values into via the constant pool So this instruction loads the constant 65, which is our four one here in the first word And then this one does the io action So that will Write out our a so now we actually have to make this work in the simulator does the simulator is p emu test see so We should load our constant Which is 101 Which is our capital a And then we do the io action which we haven't implemented yet Okay P P emu emulator Right now an io instruction is One six so it's this so we want to We want the top two bits Which is four plus two is six oh Oh zero bits 17 and 18 so this is set up dma zero one is functional pool one zero is output a now it's slightly weird and it looks so We store the accumulator at location seven And then location seven is written out to the output device and input is Stores one word of data at location seven the content location seven is then placed in the accumulator so that's going to be reg a is Get to char and This Seven equals reg a Right, so that should be fairly straightforward and it doesn't work because this is called memory So what happens when we run this step? step Something should have happened at this point. It's a step step now So firstly our program is actually Overwriting the interrupt space or are the interrupt space overwriting the program. So we're going to have to change the start address Secondly, it does not appear to have written anything To the appropriate address Reg a which is 101 octal should have gone there So why hasn't it so What is value? Value is the right number So do we go into the right? My mr. Zero off here Also, I'm an idiot which doesn't really help So this should be oh one two three one two Six zeros plus the octal prefix if I mentioned and I hate that octal prefix So the top two bits that's going to be two four no two zero one two There it is There is our way array Good, we can now write stuff to the console. This is good So what are we going to do for our test suite? Let's try testing add is going to be dead simple, which is load a with one add on to Now we want to check to see if the result is three Now we haven't done any of the comparison instructions yet But we can do that with there should be an is equal to iet so If the accumulator is equal to three then branch and go to Then go to uh this label Otherwise we want to tell it tell the system that it didn't work I think as simple as that Uh, it hasn't printed the entire constant pool The assembler framework only prints the first eight bytes so This of course This of course won't work. We've loaded one We've added on Two which gives three We're now comparing with whatever's at one two Which fails because you haven't implemented that So let's implement it So iet Wherever it's gone got to is four six Now this is a little bit weird because of the or and register stuff So it does the comparison Then the result of the comparison is merged into the decision register Based on the contents of the or and register The only thing change is the decision register and in fact now I think of it Okay, so if If the or and register is true then The decision register is bored with result Otherwise the decision register is ended with result So this then becomes do conditional reg a equals The the value at the effective address Now this brings up another couple of issues which is that my test suite is not right Because the result is going to depend heavily on whether the and or register is set to and or or And the old value of it. So we're actually going to want to do Or d to set the condition register to or And R e d to reset the condition register to zero So this the this instruction will or in the result of the comparison And hopefully give us the right answer and this of course gives more instructions that we need to implement so Or d which is what I called the or instruction Actually, let's do r e d because it's here. That is two three Very simple And we might as well do This one while we're here and this one Here we go one one and one five So one one is and d fc f a is true And one five is or d f a equals false all right, so Or d should set a here the and or register to zero Which it is R e d should set the d register to zero Which it is this should do the comparison and the result should be a positive d Which it's not That should have turned into a capital letter d So why didn't that work because these are backwards There we go capital d Which is a positive result. Therefore the brc will jump We haven't implemented brc Right brc is Again not complicated Here we go If the content decision register zero nothing happens if the content decision register is one content of the storage is placed in the instruction counter Decision register and or and register are reset to zero. So by default After every comparison You get zero And that is four two So brc so if if reg fd reg pc equals ea insin reg fd equals reg f a So you can tell remember this is designed for A kind of english language syntax. So this would allows you to say If it is five and Thing is nine go to label This then becomes if it is five compare a against five and Thing Is nine Go to label. I just put a then in there then go to label Brooke label So it translates almost literally And after the condition after the statement is complete Then the decision register and the and or register are reset back to zero so Let's see if this works step step step step brc And that takes us to the wrong Location because I forgot to read The effective address brc then goes to 11 which is halt a have successfully tested that thing So if this fails And I can make sure it fails by putting one of these in Then if I do this without the debugger it runs Toons opera 11 and stops Oh, it hit the io instruction No, it didn't that is octal that has in fact jumped into The constant pool somewhere. So let's try that again with the Uh debugger Oh, I forgot to assemble right now. Let's try it compare Invert d cd here turns to lowcase try to do the branch branch does nothing load our letter and Right Then it prints an x Okay, so if I try that again without the debugger That is exactly what we expect uh it Prints our x and halts with an error because you haven't implemented halt What halt does is it stops the cpu pending the next interrupt So that's actually slightly non-trivial to implement until I figured out how I'm going to do interrupt But we have the beginnings of our test tweet So we've done a simple edition We can move this up to our initialization code Because we know that after every check It uh should Reset these Um So what else do we want to do? well Well all of them obviously In terms of nasty edge cases, let's try an overflow So this is going to be three Seven seven seven seven seven and one Now the result here should be but we can actually test this It should be this but we also want to check the overflow and We want to check that the overflow bit is set So there's actually an instruction do this Where is it? Where is it? I did see it. Uh, I promise And or is less than Is equal to Right, this doesn't say whether it's signed or unsigned which isn't so great Here we go if overflow tov Which we're going to have to implement that's one The content the overflow is registered as one the test condition is set to one, right? Oh, and it changes the overflow register the overflow register is reset to zero Right, so this is actually a simple do conditional With the overflow register And then we reset the overflow register And there should actually also be a Similar carry. I don't see one See, I would expect it to be Here somewhere okay, so Actually, we can do this a clever away So like this so Start with this value, which is the largest possible positive number add one Check to make sure it's the right value. So if it's this and an overflow happened Succeed operators are not instructions. That's an and d. Okay Okay So here is our program So start at the top with rd red Load our maximum possible number that actually that's Garbage Why is that garbage? well Let's see what this does That did indeed add one to it and then masked off the This which should never have been there I think something might be Extending when it shouldn't be okay. Let's do the comparison Now small d because that is a false comparison. That's correct So we do and which then sets the a bit Test overflow which sets the overflow bit, which is not set So the decision bit should remain zero, which it is Branch doesn't doesn't work. So we then print our x So this isn't working for some reason. Let's take a look at The image this is our x that will be add success three Two One is the i o number the i o value here Followed by another x So therefore this is opa success Which makes this Our o400 This here's the one and this Is this value here? I think the assembler is printing garbage chances are So this is working in terms of words, which is right. I don't see any in 16s There's the lexer. What's the lexer doing? That's here Uh, here is the code that actually parses numbers Which works in terms of words. So that should be correct too So I think it might be a problem with dw Okay, so that has printed The uh All the constants it's seen so This and this should be one different and they are not So what is I can't remember how to do octal in lua. It's not that I can do it on my uh ancient fx 350 Think probably that doesn't appear on the screen No, I can't I can do hex. I can't do octal Let's try a Even cheaper calculator that one doesn't work. It doesn't have any batteries in it. Okay. This is slightly embarrassing Julia, I know does Octal numbers I just can't remember how to turn them into decimal I've got 131072. Yes, I should have known that Well This number here is not this So that sounds like the assembler has Failed to lex this correctly Oh, oh, I know what the problem is They're not Properly tell it I wanted octal Because cow goal the assembler and see use different syntax That's better. These are the right values Okay, so Okay, so we've set the a bit for and decision bit is true Because we've successfully passed the iet now we test for the overflow bit and d is still set So this works Good and eventually it hits the halt and terminates Let's just add that for simplicity. I'm just thinking the best way to do it Okay, let's just hack things for now when it sees a halt instruction. It just terminates So that has executed a program and finished successfully Which is good Okay, and we're always going to be adding tests at the top of the file because Um It makes it easier to step through for debugging them So load in a negative number, which is this add a negative Yes subtract one that will become Um Yep, that will become this Which will overflow and it succeeds Which is just what we wanted Now we should also be testing carry, but I'm not sure how to get the carry out Yes, I I do know how to get the carry out, which is With the adc instruction Been really nice if there was a test for carry I wonder if the Advanced processor has that it's a test Accumulative odd parity Test overflow. I don't see a test carry But we can do Load a with lot Add carry Now what should this be? Uh It will not Propagate a carry So it should be zero over here From three seven seven seven seven seven two four, etc That will not propagate a carry and that fails Did we implement adc? Yes, I did Does adc reset the carry? A lot of these things do seem to you are plus carry registers altered overflow carry register overflow register Content of the carry register is added to the content of the accumulator If a carry occurs at the input of the 18th bit, right Otherwise the carry register reset to zero So this is actually doing a perfectly normal ad Setting the carry accordingly So that should do the right thing So why didn't that work? Well, so After the ad we've seen that carry is set We've gone from here To here that means the sign bit has changed, but I don't think it propagated carry I think my logic here is wrong in do ad Because carry is typically a unsigned thing That overflow is a signed thing Do I just want to test to see whether the results Do I actually want to test to see if the bit above This is the bit propagated The one immediately above the 18 bit value Has changed Because that will have been sign extended here So we've got no carry, but we have overflow and I think that's correct So that's successful I think that's right So for the carry case We actually want the highest possible number And we're going to add one to it And this is then going to be compared to zero because that's what we expect And What would overflow be doing in this case? This is the stuff I hate most about doing this So overflow is meaningless in this case, but it is going to be setting the value to something So what does the text have to say about overflow? Add Overflow causes overflow could occur when two numbers of the same sign are added, which is true here Overflow causes the 18th bit of the sum to remain in the sign position and the overflow register to Be set to one What does the advanced processors say? They may have clarified the text sub edx add Overflow can occur if bit if bit 18 of the sum differs from bit 18 of the operands The overflow register is set to one. Otherwise it is unchanged That's better because there's an actual specification Now, how is it numbering bits? It's numbering bits from one Ah, yes, here's the definition of carry if a carry out of bit 17 occurs the carry register is set to one This is difficult to do in C Or we can do the overflow correctly And I'm just hoping that the obp specification is the same as the The aob abc aop Advanced onboard processor aop They missed a perfect opportunity to call it the abc the advanced On-board computer and now that's not going to work Okay, if bit 18 of the sum occur differs from bit 18 of the operands so This is left and sign bit is not equal to result and sign bit or right and sign bit is not equal to result and sign bit Can we be cleverer than that than that? well if When two numbers of the same sign are added now I'm going to assume that if you try to add two numbers of different signs The overflow register is not set so For these to be the same sign that's going to be zero Now we know it's the same sign So all we have to do is to compare one of those With the sign of the result I think that is correct. So that Does overflow However, the sign bits are different. We're adding one No, we're adding minus one to this so the Sign bit is set in both Let's make that a bit clearer Minus one is no, it's not That's minus one That's minus one Minus one plus minus one is minus two. What was this before? No, it's a I'm adding one to it. It's not minus one Right. So the sign bits are different. Therefore, I would not expect the overflow bit to be set No v flag and our test pass That's because I haven't finished writing the test Uh, this thinks the carry got set This thinks the carry is from bit 17 So this is actually Ignoring the sign bit So if there's a carry out of bit 17 then The sign bit is going to be Set if it was zero Unset If it was supposed to be one All right, so I think the simplest way to detect carry with 17 bits is simply do an add of 17 bits and then see whether anything got carried So that is You left and three seven seven seven seven seven plus You right and oh three seven seven seven seven seven and check the Top bit and that should give us our carry So it does think that the carry happened with our first add Because we are in fact adding three seven seven seven seven seven with one which is getting Four zero zero zero zero zero, so that's correct. The carry is set So you want to actually check that So if it is If the result is zero and Our code here thought that overflow got set but the Ove But our two values had different signs therefore I would not expect that to Be the case So with different signs this would end up being one so unless Overflow was set before which it's not Actually, hang on a second. Hang on a second. So this Is going to produce a zero if they're the same bit If they if the sign bits are the same So if we then XOR it with the result If it's the same No, that won't do anything useful So left XOR right should produce a zero if the sign bits are the same or one if they're different So if they're the same it's zero therefore we Invert it and use a short circuiting comparison Yeah, that's not right We actually want to do if There's some right and sign bit to say if the sign bits are The same that is if they are the same then Do that Let's throw that Oh, sorry. I had to keep you go and that set the carry but did not set the overflow which I believe to be What I expected so Result is zero We want the overflow to be false That is zero So Can we invert the overflow? I don't think we can We can test the overflow But we don't want to test for it to be one. We want to test for it to be zero so Let's go to or mode invert the result of our IET unless there is a If not equal to I don't think there is No, I don't think there is So the successful result here is a zero decision flag Or in the overflow so again the successful result is a zero for the condition flag and we want the Uh We want to test for the carry being set. So we invert the decision flag again now one is the one we want And lda zero adc test for zero Hang on we don't need to do this Oh dear This is why you don't do it like this Okay, let's change the order of things test for the overflow Right, so now one is the value we want so and with the Result we want which is zero and with lda zero adc iet zero Uh, we want the carry to be set brc carrying positive add success Let's try that so There's our value add one to it to get zero Carry gets set overflow does not set test the overflow flag No overflow therefore the decision flag remains zero invert and test the result and test the carry just one Okay, however, something fails further on can do trace one go Right, and that tells us exactly where it failed, which is program counter 51 And what it was doing Well, let's take a look at the list file 51 octal No, that's the line number 51 octal, but the program counter here is of course in hex That's less useful than you might think So we can actually Improve this slightly right. What was the last? What was the thing that? failed see It failed here overflowing positive add so right Load this add one now Carry is set which is correct and overflow gets set Because the result is too big to fit in a signed 18 bit number All right, I believe that to be correct so we Check to see whether the result is this which it is We check to see whether the overflow bit is set which it is It thinks that the carry is Uh The wrong result so this should work That works Okay, this this is making progress It's painful, but it's making progress uh Let's do a test for Shift left. This should actually be easier as it's a much simpler kind of thing So we've got the top bit set One two one two and the bottom bit set shift instruction called shuff shuff one and what does this do flags wise The lower order six bit blah blah blah counts negative is shifted right The count is positive the accumulator is shifted left the overflow register is set to one If the sign bit of the accumulator is changed during the shift so The result is going to be Uh, the top bit that four is going to go into the overflow bit and We want to compare the result to 00002 and I believe that's it success shift right Now shift right does not do anything with the overflow bit Or is it The overflow register is set to one if the sign bit of the accumulator has changed during the shift now The question is does this apply to both directions that is only apply to left shifts What does the other doc say? shift The count is negative the accumulator shifted right Contents the accumulator sign bit replacing vocator positions Oh, right. So when you're shifting right because it's a sign shift you are never Uh Changing the sign bit. So the overflow register will never be set. That's good so All we're going to do is compare the result with Uh six 00001 oh this is our success Oh, um, we do want to test the overflow reset overflow reset overflow test overflow Invert we wanted to make sure the overflow is not set and This is our success and that fails here Presumably Well, let's Try it I haven't assembled it that rev is not appearing Shift, okay. I forgot to mask off the result. Is there a reset overflow? I would expect it to be called rev reset overflow rob Yep Uh, okay. The reason why this is not failing with an error is due to a nasty misdesign in the assembler framework It's not actually the framework's fault. It's the syntax I'm using Uh, allows you to do that. That is a valid label declaration with no colon on the end that means that it can't tell the difference between a Unknown label which it needs to set and an instruction opcode. It doesn't know so it hasn't actually So what it's done here is to find a label called rev but without actually Producing what we want. That should be rob I think we still need to emulate it as well rob is Oh seven just to check to see whether it's a op Instruction At oh seven, I think it's Not on seven Oh seven rob. Yeah rob. Okay. I have implemented that So what does this do of reset overflow? Shift by one produces this result no overflow Okay So that works. We now Tested shifts in both directions So double shift left Now a is the high bit. So that's going to be for 00 001 E is the low bit That's going to be 4001 again. So Double shift left by one the result we Believe that we want the overflow bit set In fact, I don't think that's necessary because The last time we touched the overflow bit should be a tov Where's that double shift? Shifted by double shifted And yeah, it does do the overflow thing So double shift left by one so We wish to well first we're going to set the Check the overflow bit is set and A the high bit Should become The top bit gets shifted off This bit gets shifted left to a two The top bit of e Doesn't get touched because it's the sine bit So and now we want to get e into a I can't remember whether there is a instruction for doing this. There isn't the advanced processor Let's see what Here's these exchange instructions We want exchange accumulator and index which is 25, but I don't think there is one on this Yeah The obp only goes up to 23 octal So in order to get e e into a where you can do stuff with it We're going to have to Write it out to a temporary Location load it back into a Then compare it now The four does not get shifted But the one does so it's going to be that double shift left success lda I'm getting these letters entirely the wrong order But anyway, we've got x y z. Okay, so right load this value into a Now the same value into e shift left by one That looks like what I want Actually, we do want to test to make sure that this bit here goes into the Top bit there. So that's going to be a three And that's going to remain a four three four. Yep, okay so overflow is set and That value and And Store e to the location load a Compare yep And all the tests pass good Double shift right to Gonna use the same values for simplicity dish minus one Uh Tov cpd and So a is going to be e sign extended shift so for like that and sd temp lda temp iet Now this one is going to go into the top bit of e Well, there's the bit of e that's just below the sign bit so 110 as the top octal digit is going to become 011 no, it's going to become a 101 But the one here is going to get all in so that's going to be 111 7 like so Right success You Failed. Oh, yes, I forgot that's the Uh the sign bit gets propagated. How big is our program now? Well, it's going to be painfully inefficient because All these constants are going to get inserted into a massive constant pool so the actual Let me see. We've got six one hex times three bytes right, so this Is here So the constant pool is all of that And there's a lot of duplicates We can fix that later. That's just an assembler bug Okay, that's actually a reasonable number of tests. Oh, we should probably do cycle which i'm not really Well, so cycle is fairly easy. Let's do this test for cycle cycle, right? 401 psych one Cycle should Set no flags and it's a simple bitwise operation It works just the way you might think That's like left it's leaving the low order position entering the sign position. Yep And it doesn't set any flags. So that should be easy So compare So the result is going to be that well by that you see cycle left success The this is the most octal i've ever done in my life Can't say i'm enjoying it much error line one expected and identifier Can't you remember the syntax to my own assembler? Undefined symbol i o Let's do that Okay Okay cycle right d a o o same number cycle minus one And this is going to become like this Double cycle left double cycle is dcy Yeah, nothing exciting there so the uh a which is in the left hand side will become over three and temp temp uh e is going to be car and if four is shifted left like that and double cycle right is going to become And e is going to become six oh Double cycle right success That fails. Why does that fail? Okay That's because I shifted that the wrong way Interesting it still fails interesting that Is wrong Where's my double cycle? Here we go So it's shifting right What's this one? So amount here is minus one so 36 plus amount is 35 Which is two 18 bit words minus one So the bottom bit Get shifted left by 35 So I think that bit is being put in the right place that is The top bit of a however a itself is not getting shifted correctly So that just should have shifted it right by one Okay, let's well that is Here is our value Which is not the right value Right, I know what happened It did a 32 bit arithmetic here Right now that works So, uh, this had actually failed to put together the 64 bit value correctly by truncating the result of 32 bits Okay, well We've now verified that quite a lot of this works So what instructions are we missing? Here's the instruction table BRM that is the branch to subroutine That sounds like a Fun one to implement So Subroutines on this machine are weird. Let me just think about syntax. So I think this is actually We go The content in the instruction counter plus one is stored at the effective address Unless that address is protected Right, so this is basically a store Or Followed by another more interesting thing So what we're going to do is we're going to write The current program counter the current program counter has already been incremented Into the effective address And then the current program counter becomes The address plus one that's all you do for a subroutine call So over in our test suite we need a subroutine So we're going to implement one down here lda one test subroutine This is the return instruction Now you notice that there is a dw0 here This is where the return address gets stored when the subroutine is called So you call you make you execute the brun instruction. It puts the return address here Jumps to here executes the thing and then this brun instruction Loads the return address from here and jumps to it so This is going to be a subroutine Compare with one says subroutine should set a to one uh yo t calls subroutine Success like this now we do also need to implement uh bru bru is Two that's here And this is very simply Now notice this means that you can actually call subroutines without needing a stack of any kind Which is pretty cool Let's assemble that right brum 217 We should end up without programming counter 218 Well 220 because this is octal uh load one Which is correct bru 217 And that puts us right back at address four where we should be Excellent, we've got subroutines one two exu exu execute Oh, this one's exciting Read ea instant static void exu This executes an instruction once And in fact, I've set this up a bit incorrectly. So what we actually want to do is put this here So what this does is it reads a value out of memory and it executes that as if it were an instruction Where is exu? Content of storage effective address is used as the address Wait a minute. Wait a minute The content of storage at the effective address is the address Of the instruction to be executed the instruction counter is incremented by one unless it is changed by There's an extra level of indirection. I wasn't expecting That means my cleverness here is less clever than I thought it was going to be Anyway, let's do So this is actually going to be this So two reads We compute the effective address We read that to get an address And then we read it again to get the instruction So static void to exu instant Now if the instruction is itself in the exu What's an exu one two and four zeros Do nothing otherwise Execute it just prototype that Right. So now for our test suite Okay, so The assembler will put lda one in a constant pool And lda one is the address of that lda instruction Okay So assemble the test suite H L R T right, so Load zero This should do nothing It has done nothing load zero This should do something it should set eight or one which it has good That works Not entirely sure what this is for and I suspect neither did the designers because exu has been swapped out in the advanced processor For a completely different instruction So I reckon that they decided it just wasn't very useful Uh, okay two two We are getting dangerously close to multiply and divide But not yet x n g t If subscript is not greater than now and set the decision register This is useful for small loops So this is going to be do conditional If the subscript register is not less than It's sorry is not greater than so less than or equal to noun Then do the conditional thing with the or and register test suite So Is there an ldx? I think there is an ldx So we're going to It's not greater than why don't you just call it less than or equal to so If x is less than or equal to One and If it is less than or equal to two If it is not less than or equal to three and let's see It's going to be success success So x is one Okay, so Is not greater than three as easy as it is less than or equal to three Yeah, I've got this backwards so if it is Less than or equal to zero Which is false So flip that's correct That and that Good Two Six Oh, we've actually there's two four in here Which is sub and it's exactly the same as ad That this is the one I got from the advanced processor I do believe it's there in the obp, but I could be wrong Where's sub begins with an s so it'll be down here sub two four Subtractions perform by adding the two's complement of the content of storage to the accumulator and the carry is forced into the lower order stage of the adder um So this is the same Annoying stuff again Uh So the carry is forced in This suggests this is something to do with Borrow bits Well, I can do the simple Things let me see Uh overflow can occur when two numbers of unlike sign are subtracted So that will be this overflow causes, uh The overflow register to be set to one and the 18th bit of the difference I am not convinced. I'm understanding how this works It also doesn't say how overflow is detected so So this detects whether they the two sign bits are different I think I now need to Let's just try to see to be honest That's sweet So we are going to subtract one from zero Now I expect The carry bit to be set The overflow flag to be clear because nothing has over flown so Overflow flag is clear and The result is minus one and LDA zero Uh ADC And the carry bit should be one BRC sub positive Success Right, so what's this do? Um, I haven't put Sub into my disassembler anyway right so That wraps around Which is correct. I'm due to the right result carry is set Overflow is not set. I think that's correct However, the test fails Where did d go to zero? It's Compared Against the wrong I thought the result was incorrect So it's been looking at two seven three Which is indeed seven seven seven seven seven seven So also, I noticed that this alignment is incorrect. So let's just fix that as well I'm willing to bet that the value in memory isn't Right Now that I don't truncate the value and printing it. I can see that it's all all garbage So that will be That's a u in 32. Why would It be sign extending 2468 Oh wait, that's octal Right, I know what's happened. It's a bug here you This is actually emitting bogus data It's Like if I look at the image This will be that minus one constant Uh Six hex digits is too many to fit 18 bits. This should be Andered with the appropriate value, but the assembler can't do that because the the uh The framework doesn't know that it needs to So instead what we'll do is we'll just trim here Just to make sure a correct data ends up in the emulator There we go. Now it works. So sub negative We're going to start with Minus one and subtract minus one Now I expect No overflow carry And the result is zero fails Why does it fail? Okay Carrie was not set but a carry clearly got propagated. So the carry detection code is wrong so, uh If we mask off the sign bits we get 3777777 37777 No, that's correct. There is no carry because it's just subtracted one value from itself resulting in zero No carry got propagated That's correct So carry of zero No overflow right result good I want to check overflow So The smallest value we can represent is this that's a negative That's the most negative value you can get and we want to subtract minus one from it This gives a value that is too big not too small So overflow will be set the The correct result Is that plus one which is Hang on. Hang on. Hang on. Yeah, that's not actually testing the right thing. This Is the largest possible value We subtract minus one. I add one to it that rolls over to this which is too big overflow gets set And a carry gets propagated Oh, I oh undefined symbols. I oh So what does this do? No, no carry gets propagated that perfectly normal That overflows, but it doesn't carry. So that's zero and the v flag did get set right sub overflow negative Largest possible value Subtract one from it leading to three seven seven seven seven seven No carry overflow Trying to remember which letter comes before l k sub overflow negative success like this So the problem is that uh Some architectures when you subtract the carry is treated as a borrow bit rather than the carry bit and this reverses the behavior So because i'm writing the test and the emulator based on the same interpretation of the spec Then I could be getting the behavior completely wrong even though it's passing the test Which is why I wanted uh Someone else's test suite okay, so We have this we've just subtracted one to this This carry is set, but I don't think it should be And overflow is set and I think it is it should be so that's not right So the carry got set because No, that is correct because the sign bit isn't included as the carry So in fact that's That's treated as a zero For purposes of carry calculation. That's treated as zero subtracting one. Therefore carry is set. That is correct According to my interpretation of the spec okay Let's call sub done It's at least self consistent So over for the here again What's two six ilt If less than Now this is one that I need a bit of They changed the name ilt now and if the content of the accumulator is less than the contents of storage Then conditional it doesn't say whether it's signed or unsigned which is important So that is opcode to six. I think this is test accumulator less than Emulator these are the mine of test accumulator less tal Contents the accumulator are less than the content of storage at the effective address It doesn't say Great. Well, it's either signed or unsigned I'm going to make the executive decision that it is signed Because a lot of the rest of the architecture Seems to have a lot to do with signed arithmetic So that's going to be do conditional sign reg a Is less than sign read ea insin And I think that's all there is to it Need to tell it how big the words are okay That's sweet so if a which is Minus two If this is less than four Okay, so two This is less than Four, which is true Okay Minus two If it's less than four, which is true Yep, I should probably do a false case as well Yep, that works Okay, three two Is lds load scale that should be simple I think that's called sc Yep Sc is the register used to indicate where the decimal point is when doing Floating poor when doing fixed point arithmetic using signed um Using multiplication and division Let me just see Let me just find the lds instruction and they renamed that one. It's the wrong document lds There we go Low order six bits of the content are placed in the scale register Is it signed? Let me just take a quick quick multiplication and division Okay Automatically scaled by arithmetic shifting the accumulator by the number of bit positions indicated by the contents of scale register If the contents scale register is negative The shift is right the contents scale register is positive. Okay. It is signed so um, and We also want is it called sts We all register Don't have to implement those at some point ssa Content scale register is placed in the low order six bits 0020 So this is ssa Reg a equals Now we want Six bits two octal digits Because this says that the high order 12 bits are set to zero. So uh This lds effectively sign extends ssa does not That's sweet. I mean there's nothing much to this lds minus four ssa So this is going to be minus four and 77 vrc success Loads the scale register Hmm That is actually nominally correct, but let's make that Let's make that a little bit more elegant And ssa puts it back into a 74 Compare yes, that works 44 um I think this is going to be the dreaded mull or div Yeah, it's mull Okay, let's do mull Let me see contents of the Content of the accumulator the low order 17 Okay, it's an 18 bit by 18 bit multiplication Yielding a 36 35 bit result The high order 17 bits and sign of the product go in the accumulator The low order 17 bits and sign of the project product are retained in the extended accumulator So both accumulators with a and e end up with a sign bit so yes 35 bits The double length product is automatically scaled by arithmetically shifting the accumulator and The 17 bits of the product in the right by shifting the result The number of bit positions indicated by the contents of the scale register The sign bit of the extended accumulator is not shifted The scale register can be positive or negative The overflow register is set to one if the sign bit of the accumulator is changed during the shift Okay, this does not actually look that bad Div is going to be a nightmare, but this doesn't look that bad so So we're going to sign extend reg Uh, let's just do this for the right hand side So this gives us the signed result We're now going to shift it So if it's positive This is actually This is this code. Um, I think I've got the the shifts wrong Let me just double check the wording. Here we go The overflow register is set to one if the sign bit of the accumulator is changed during the shift So it's not whether it's changed between the beginning value and the end value At any shift position if the sign bit changes Then we set the overflow bit Okay So The old sign bit is this Where's mul? Here it is Uh, positive scale bit Is left shift by one If the result Sign bit Is not equal to the old sign bit Set the overflow bit For a negative sc we shift in the other direction because it's an arithmetic shift The sign bit can never change So now We set reg a to the top part of the result And we set reg e to the sign bit which is in reg a And the bottom 17 bits of the result All right Test suite So Let me see our scale factor is going to be Zero We are going to multiply Now this is going to be a simple low Bit, so let's just go by two by three Which should give six So the result here is going to be no overflow No shifts are done, which means The result will be in e a is going to be zero s t e temp l d a temp i e t six Simple mulls s And we got up to d so I've got e f and g and then I have to start with the uppercase letters Let's see what this does All right, right load the shift register with zero two by Three is six No overflow a is zero And e is six That works Okay, so now I'm going to do a slightly more complicated multiplication I'm actually going to multiply these two values But in a fixed point fashion So our scale is going to be that these are both two and three The scale is going to be Nine bits And that should be a mull So no overflow should happen a should be zero And e should be Okay, so Okay, that's nine an octal There are two values And that has not Done what I expected I think this should be minus nine Uh the sign bit is negative and the shift is right. Yeah Okay That's cool. We need to also probably need to do it the other way around scaled positive mull Trying to think of what this represents What does a positive scale mean? Well, you could use it to get the result into the a register. So let's try that So this should be 17 So we expect the result to be six. It's then shifted left 17 Which because the sign bit is not included in e that should cause the result to end up in a So No overflow a is six e is zero okay to There you go, and it's done the right thing. Uh, I should probably think about Overflow It occurs to me that according to this specification overflow only happens during the shift Not during the multiplication So if the multiple oh right the multiplication itself cannot overflow because the result Is 35 bits wide So we're going to shift left by one This is going to be I'm going to shift left by 18 Our values are going to be that No, because that's negative That which is positive and we're going to multiply that by Two which will shift it left by one giving all fours No, we're not. We're just going to multiply it by one Does nothing, but then the shift should Push this off the left Uh, and we want to reset overflow Okay, so the result is overflow should be set and a should be That and e should be zero scaled positive overflow mol success And i'm also going to need to test negative numbers, but that should Be fairly straightforward So reset v Scale value of 18 which is two one in hex load that multiply Okay So we have multiplied two by one And then we've shifted left by 18 I forgot the sign bit is propagated in the bottom So that's over flown But the overflow flag has not been set I Think i've been slightly too clever there. Let's try that Still is not set Okay, so what do we got here? That's our raw result Uh 20000 octal multiplied by one old sign is zero Shift left by one Um, okay, so this is going to shift by 18 bits. So So you can see a result getting bigger So Because this is 35 bits that's 12 octal digits one two three four five six One two three four five six, right that that two is actually in the sign bit Uh, I Think these are wrong. I think that wants to be like that Okay, that's better So it's now set the sign bit our results are how we would expect them to be good And we should probably do a Mixed sign just to make sure this works So this should be minus six Overflow should not have been set a should be that And sd temp This should be minus six Okay, we've now run out of letters. So let's start with the capitals Good Okay, I think mal works that wasn't as bad as I thought it was going to be to be honest Okay, uh, I think the next one. Yeah the next one is going to be div So I think I will actually Again, I am solely running out of time. I thought I was going to be done with this in a single session So i'm not going to get stuck in with div. So let's have a look at some of the other smaller operations Okay This one I can do Not Does nothing I'm not even going to bother testing it igz If the accumulator is Greater than zero I think here we go igz If is positive if the sign bit position bit 18 of the accumulator Contains a zero test condition is set to one. Okay, so do conditional reg a and sign bit So If the accumulator contains a zero So this will be false lda one igz brc igz success Okay, next is five If parity odd If the number of ones in the 18 bit accumulator is odd then the test condition is set to one now. I think there's a standard function for counting the number of bits there's uh There's ffb ffb There we go ffs find first set bit in word Yeah, I thought there'd be a reference here to the count, but there isn't Okay, so this is just going to be If If the number of ones is odd that's simple enough test sweet so This is odd and actually let's go the other way. This is even and lda one See Two of course has one set bit that has two set bits Yep, that works Okay, one two set page ldp Contents of bits 13 through 16 of the accumulator replaced in the page register I'm just trying to think how many bits I need to shift for that Because they're using weird accounts It's these four bits Uh, oh, yeah uh 12 tricky thing here Is testing because as soon as I change the page register Then I lose access to all my variables And the constant pool and then there also should be a Set from page register Maybe there isn't So one reason why everything is indirected through memory Is to allow you to refer to an 18 bit address With only four octal digits Well, the 16 bit address The idea is that Different programs would be linked into different pages and the whole lot loaded as a single binary image They can refer to each other by simply updating the address in memory While not touching any of the code the code is Relocatable between pages So how am I going to test this? Each page is 12 bits So 4k pages How big is our Image not big So we should just be able to do so That's not done what I wanted See I was expecting to see Lots of padding at the bottom Oh Yes, uh org doesn't actually emit padding Uh org just changes what the system thinks is the current program counter I know what I can do So go to page one load of value Go to page one Store it at address zero Go to page zero Can I load that no over and out of bits but what I can do is Load that because that goes via a constant pool and I'm allowed to use constant pools because p is this is zero Uh ldx 4096 that then allows me to do Zero comma x and then this should be 1234 So what we've done is we've used the page registers to store 1234 Then we've read it back using a direct memory reference and checking to make sure it's the right thing Success Success in syntax error What's wrong with that? Unless it's complaining about that It's just got the wrong line number No, it doesn't it doesn't like ldp ah ldp is a simple Contents of bits 13 through 16 of the accumulator are placed in the page register Right So what we're actually going to do is load that into e lda one ldp story lda zero ldp And that allows us to load our To do our right Okay, that's assembled lde 1234 which is now there loads you load one Uh that didn't change the page register because That is The page register is loaded from the top For bits of the bottom 16 bits of the register i.e. it's an address it just pulls the top four bits of the address out Okay Right p is now one That should store the right value Oh, oh, oh, oh I can't do that That's a constant pool reference How can I get a zero without referring to a constant pool? uh Well, we've got One Hmm Yeah, like that. That's vile, but it should work. So it's set page register store e Load a with the value we wanted subtract it from itself which gives zero Load p Okay Load x with our address D reference Oh, we got the right value And let's just check Yep, and our value here At real address zero hasn't changed And if I Do this You can see here is our two three two two value right ldp works one six what's one six Uh, that's more i.o wait No, that's the Main address that's the The noun form what a One six in the simple instruction form one three one four zero Six exit Okay exit is interesting Exit does a system call What this does is it fakes and interrupt Uh Interrupts work by the system Uh Saving the current state of the cpu at a particular memory address which varies depending on interrupt And then it will load the new state from a slightly different area So This is actually something So we're actually just going to queue the interrupt It's interrupt number 16 but Yes, this calls it 16 as well. So let's just queue interrupt 16 And for now, we're just not going to do anything. We're going to deal with that later After div Two one Is ie z which I believe is If equal to zero yep, so let's do conditional reg a equals zero That's all the simple instructions Complex instructions. We have three remaining We've got six four which it's div Six six which is igt And igt is going to be the same as ilt, but the other way around so we're just going to copy that code Seven two is tin which is return from interrupt There we go resume content of the storage at the effective address is used as the starting address Of an interrupt storage area And this document doesn't mention interrupt storage areas, but it mentions them. It doesn't define them This document Does define them. I'm going to assume that the format hasn't changed And here it is So there are 16 interrupts from zero well 17 interrupts from zero to 16 Each one is defined each one has this block of eight words When an interrupt occurs It writes the state into these four words And then it reads the state from these four words And the tin instruction reads the state from the first four words So This is just going to be Use tins return from a interrupt Now let me Check the wording The content of storage at the effective address is used as the starting address of an interrupt storage area so It's that double indirection thing UM32 address okay So emulator interrupt is going to have to check the various Priority and lockout registers to see whether we can actually handle the interrupt If so We calculate the address of the Interrupt block We write the state to it And then we will call do tin to read the state That we want to go to So there's actually very little code in the interrupt thing. This is an amazingly simple machine That would be it That's like nearly done Okay So Oh Yes, uh, I should probably I should probably test the instructions. I just added but then none of them are interesting They're just copies of other instructions with slightly different state. So I can't be bothered So let me just commit that And I'll probably call it for today See you very shortly Okay, let's finish this off There are only two more things to do but they are quite big things which is the divide instruction which goes here and the The interrupt handling Which is these two routines here I there's also some bug fixing the needs doing because the shift and cycle Actually just the shift operations aren't correctly calculating the overflow register And we also need to do a little bit of arrangement of the startup sequence I did some reading up. I found this Nice description of the interrupt system in the aop documentation The way this cpu works is there are 17 possible interrupts numbered zero to 16 Interrupt zero Is the one used to start the system up Interrupt 16 is the one used as the system called the one that the exit instruction Which is here Invokes the reason why it's called exit is this machine supposed to run a timesharing executive kernel Which uh schedules between multiple different tasks to different priorities And uh when a task terminates it will call exit to return back to the Uh kernel This may actually Also be used for de-scheduling as well if it's cooperative as the The memory protection register only ever gets loaded from Uh an interrupt description block Then this will allow the Uh the scheduler can Set the appropriate memory protection for the task it's running And then the exit routine because it invokes an interrupt will then reload the memory protection register for the scheduler So The way interrupts work is there are 17 of these blocks That uh live in memory from zero to 200 octal And when the system starts up it will invoke Uh It will set the registers to what's in this half of the block that is Uh when the interrupt occurs this is where they're loaded from which indicates interrupt priority content of page d overflow and carry The memory protection register and the program counter note that it doesn't contain s e or x uh Or the scale register Now i am assuming that the obp's behave the same way as the aop But uh, I think that is plausible so We're going to need a Interrupt priority register, which is Uh 15 bits wide Because this doesn't include interrupt zero and it doesn't include Interrupt 16 is that correct I wish their number bits from zero it makes them so hard to understand So this is actually zero to 15 which is 16 interrupts So I actually I think that this one Does correspond to the exit interrupt Which means that if you call exit when interrupts are blocked it just cues the interrupt continues execution I think That seems odd, but There you go Okay, so Uh, let's add a interrupt priority register 16 bits wide Let's call that i so our tin instruction Tin loads the execution state from a block It's used to return from an interrupt you point it at the The old values which are saved when the interrupt happens and it reloads them and continues So This is pretty straight forward I wonder what happens if She's want to check something because I believe this should ignore the memory protection register and the Uh Ticks tie Top where's tin? Oh, they renamed it for the a op I believe this instruction should ignore the memory protection of register and the page register. Here we go Resume from interrupt called tim Really tim with an m okay Uh content authority effective address is used as the starting address for a forward save register That suggests that The page register is used. Otherwise, they wouldn't be talking about effective address and Memory protection register presumably Oh, this this is a read. So the memory protection register is not invoked Okay That's sensible enough. Where's that table? read i read sl from and read the program counter from here And I just realized that there's a bug it should be because it is not The previous code was not honoring the page register Okay, we need to pull these fields out So the page register is four bits starting at bit 12 because of the piciculia number equals However, these are flags. So that is by six Overflow is seven D is nine So I believe that this one Would be where the and or register goes which is not saved Okay, so that should be correct So on startup therefore Do we need to prototype this because we're about to call it on startup to do the appropriate things? That wants to be zero and do tin four I believe Overflow register is called v and it needs a semicolon And this is reg pc Okay Right now our test suite So we are going to So this is the header at the beginning of code, which is these four Uh unused vectors followed by The new interrupt priority we have to figure out how the interrupt priority works Uh page d overflow and carry is zero Storage limit register is What did I set that to that? Then we want the address of which to start execution Okay So we assemble run it And we see we have Uh sl is set correctly Program counter is set correctly here at the order instruction So we should be able to run it. There we go. We can now return from interrupt We now need to take interrupt also I'm actually going to do a bit of poking around Now the documentation for the io instruction In the obp Stuff I can find it There it is It says that uh it outputs the Content of the accumulator with the content accumator is stored at location seven Which is here So Once you've overwritten that how are you supposed to restart the system? I am not actually terribly sure about that I'm wondering if it's one of the things that they may have changed in the Uh With the oap aop Which means that the The assumption I'm making which is the interrupt system works the same way In both systems may not actually be valid to find the io instruction, which I think they've renamed So the io instruction is So the iop instruction No, it's not the io instruction octal 16 So Let's look for that Here we go output two Yeah, they've changed this completely. This is totally different logic so Uh I have a bit of a suspicion that the obp and the aop start execution in different places there's some Stuff here about Linking programs together, but I think it's assuming that the That there is a loader on the system Which is supposed to start things that contain the uh The executive Now this is io interrupt. So 15 bit register for the obp what does it say about Interrupt one has top priority This interrupt will be used to initiate program execution Okay, that is different from the aop Right that makes sense so So this is interrupt one This is interrupt zero Which is unused on the obp. That means that the io stuff actually writes to this address Uh, this one is is address seven So we are actually going to want to Change our startup code to execute The appropriate routine Let's try that Okay, that has worked We are in the right place our test suite runs all right now There is a 15 bit register in the io unit which stores interrupt requests And there is another 15 bit register, which is interrupt priority register So let's change this to irq irqp So the priority register gets loaded from the data block So when an interrupt happens We want to Cue the interrupt Like this But I don't actually think we want to do anything else at this point If an interrupt is queued when an interrupt is already present, I believe it gets lost Interrupt sent to the cpu When the request bit is set from the hardware And the corresponding bit of the interrupt priority register is zero Should two allowable interrupts simultaneously request interrupts io That indicates the order in which they are handled The interrupt priority register is set when an interrupt is sent from the io unit to the cpu Or when cpu into executes exit when the cpu executes resume The one exception is interrupt one has top priority and cannot be locked out Um As each request is serviced the corresponding bit in the register will be reset Okay, so an interrupt happens We set this bit Then when we service interrupts we reset that bit so Interrupt priority register is set when an interrupt is sent from the io unit to the cpu Why would that update the interrupt priority register? Surely the interrupt priority register will be Oh, right This is talking about when the interrupt is handled and we read the interrupt Now we read the new value of the register from Uh From the data block that happens that happens here Okay, so I think we now have enough data to be able to handle interrupts And that is going to happen Here just before we process any Before we process each instruction So We want to Scan our interrupts Interrupt one is the highest priority The interrupt priority register is how you enable and disable particular interrupts Okay, we we need to scan the interrupt bit starting at interrupt two Okay The interrupt numbers used by the documentation are start at one and go up to 16 The interrupt numbers in the in the bit, however Interrupt one is going to go into bit one according to the documentation which is bit zero according to everybody else So hence the minus one there So it is 15 bits wide so if If there is an interrupt pending and The interrupt priority register Is Allows us to take that interrupt Here the corresponding bit the interrupt priority register is zero So that's actually a not there Then we take the interrupt we reset the appropriate bit in the irq flag to indicate that we have taken it and We We use We do not wish to do this here We wish to do this here that is before we read the actual instruction and of course We need to save the old state. So this is going to be so interrupt one which is i equals zero goes into octal one zero and This code this line will actually Context switch to the interrupt handler So now we need to save the contents of Our interrupt so Don't want to use the wr instruction Because we are ignoring the memory protection register So we are just going to write the The old value of irqp The old value of the memory location register The old program counter and This is going to be the value of the status Bits like that So I think I think that's right So how are we going to test this? Well, we can't actually provoke interrupts except I mean we can't provoke real interrupt But we can provoke assistant call so this is Interrupt priority zero interrupt are on Flag's word is zero memory protection word global access and The actual entry point is the system call Which we're going to put down here And we need to save Our state We also need to save the scale register is ssa Actually, this is a system call not an order and not a normal interrupt so that we don't have to Save everything, but let's just try that Well, we then do the work and then we have to reload everything back again So load the scale register load x load e load a And in order to return from the register we do Let me check the wording for tin Content at the storage at the effective address is used as the starting address on an interrupt storage area So it wants to be that And I think that should work So we should just be able to do exit. I will actually set the registers So now we want to compare these so that a should be one and and e should be two and And x should be three The thing is if x it doesn't actually do anything then it won't change any of the registers Honestly, that's good enough Okay, and now what happens when we run this? right board red Load a with one load e or two load x with three that didn't Do anything But the tests will still pass because It didn't do anything Yes, actually we do need to make this a bit more intelligent. Let's have our system call Add one to a subtract one from x and leave e as is so Add one to a a should be two E as is Subtract one from x that should be two Right, so it it has definitely not run any code because our registers are unchanged So this suggests there is something wrong here. We should print the Interrupt bits 15 octal bits is five Five 15 bits is five octal digits Which is too big that's a shame don't think there's any way in which I can Get more space sadly Okay So run exit that Have I set the right bit? Yeah, I think I have not set the right bit So that should have set That was irq 16 So that should have set the 15th bit So not one two three four five six seven eight nine ten eleven thirteen fourteen fifteen 16 So that should have done one shifted left by 15 So this is actually scanning bits One two six scanning interrupts One to sixteen Let's actually make this a bit clearer All right Do great emulator Interrupt okay interesting can't type into the debug into the debugger window Okay, we're here so Have I just managed to completely fail to do mental arithmetic? I have a slight touch of the numeric equivalent of dyslexia which makes arithmetic Rather difficult Um So If irq was one we want to set the bottom bit so One minus one is zero one shift to left zero is one so zero one two three four five six seven eight nine ten eleven 12 13 14 15 right. Okay. I am just completely failing to Count correctly That is setting the right interrupt. So why is it not detected? so it should Have spotted that This bit is set And spotted that this bit is clear in the Priority mask So taken the interrupt So this is it scanning the interrupt bits Ah right We've done the scan before the we've executed the instruction This means that our execute happens Our exit happens if it goes round and hit the debugger Before it's scanned for the next interrupt So if we do it this way round Then it should take the interrupt There we go Yeah, that would out the old logic it would have worked, but the Debugger was being invoked before the interrupts being processed. So it was displaying The wrong instruction. This is clearer All right. Now. What does this do? We should now be down In our interrupt handler here. So we add on one to a We subtract one from x that doesn't do what I want. We don't have a test for x yet now for adx But we then call tin which should put us back at instruction 216 Which it is and The test fails because That's a good point. Why does the test fail? I actually forgot to put the Uh Failure case here. So that should actually output e doesn't So that should fix adx Yes Let me jump back to here. So we compare against two And now we are doing the ldp Which now passes. Okay, that's good We have exit Working and we should also have as a corollary of this general interrupts working So we do want a quick test for ldx for adx I forgot to assemble it right sdx into that address load into a Good And there aren't any flags deal with so that should be straightforward All right, the last one div Oh boy so Hardware divide on these early machines. It doesn't quite do what you think it might Apart from anything else. This has got the automatic scaling which is annoying Uh So the content of the accumulator extended accumulator automatically scaled by shifting them the number of bits In the scale register The content of the scale register is negative the shift is left So up here The content of the scale register is negative the shift is right. So this is the reverse The shift is left for the shift is left with zeros filling. Yeah, yeah, yeah If the sign bit the accumulator is changing the shift the overflow register is set to one If the content of the scale register is positive the shift is to the right and the sign fills positions All right, so let's do a couple of helpers okay, so This wants to be left shift result equals left shift result reg s c else result equals right shift result minus reg s c static avoid to div In the scaled accumulator and expanded accumulator Form the dividend which is divided by the content stored the effective address So we want to read the right hand side of the expression and do the divide Okay, so the first thing we need to do is to compute the left hand side which is the Which is uh, that should be reg a reg a shifted left by 17 bored with reg e with the sign bit chopped out right so If the scale register is positive The shift is to the right Oh, yes, and also We need to sign extend to 35 bits if reg s c is negative Shift left okay Now we want to do the division So that should be the left hand side divided by The sign extended right hand side The overflow register is set if the content of the accumulator That is the result result greater than or equal to the content of storage So if we divide three by two with no shift So in this situation accumulator is zero and extended accumulator is three to form the simple integer three divided by two gives one Right, this is referring to the actual accumulator After the shift has happened that is the top 18 bits of our 35 bit value so if The overflow register is set if the content of the accumulator is greater than or equal to the content of storage if lhs 17 is greater than or equal to the contents of storage set fb otherwise We do the Okay, let's uh, just do this So this gives us the quotient and the remainder the signed quotient goes in the accumulator The signed remainder goes in Reg e So in fact we should just be able to Reg a Reg e Now the remainder has the same sign as the dividend and has a magnitude less than the divisor I Can't remember what c does with quotients, but let's Go with that Expected identifier before numeric constant All right, so that should be our div instruction. Let's try it out So we're going to divide three by two So lda zero lde three div two Now we want the overflow flag to be set Now to be clear so get overflow and invert it and We want the quotient to be one and We want the remainder to be One as well Okay, so a zero e is three Do the divide Whoa, one one and no overflow good um Just realized I did remember did forget to set scale register, but luckily it was zero anyway We are going to divide We're doing the same thing Except with a 17 bit scale We expect overflow to be clear I expect a to contain one fact we should get exactly the same results as before so Do the divide and we get nothing I think I've used the wrong direction No, no, I did get I did have the right one positive scale Causes div to shift the left hand side right Which okay, we're here interesting why I didn't why is that zero So a is three that should be shifted left. I have have Not got the right type for my sign In fact, that would be cheaper if it was a macro never mind But it's still not working So That is our a shifted left I think my sign extension is still broken for 64 bit values Yep, it is There we go one one and no overflow good And we also want to check in the other direction This will shift left on negative div Conta contra scarator is negative the shift is left so If we shift by one so this will get Turned into a six divided by two should be three remain to zero With no overflow a will be three If the cumulative is zero Okay, now what does this do? Not what I was expecting That's because that should be negative There we go Quotient three remainder zero no overflow good We should check signs minus three by two Should yield minus one has the quotient and with no overflow Think no overflow The content of the accumulator is greater than or equal to the amount in storage accumulator is minus three Okay, that's the right way around so no overflow and the result should be minus one and The remainder should be Same sign as the dividend The dividend is the right-hand side. So the remainder should be one Okay Now what's this doing? Well for a start these values are kind of garbled But I can see that the remainder here is minus one, which isn't what we wanted And that number is just wrong hmm The result here is minus infinity Well minus Int min for this representation when that should be one and the Remainder is minus one, which is wrong so Well these numbers are wrong for start that should be that way around Okay, minus one minus one that should be minus one one So we need to adjust this You see back in the basic days there was a standard function called sign That returned the sign of a value So either One or minus one depending and it was really useful for this sort of thing We've got copy sign, but I'm not sure that does the same thing These functions return a value whose absolute value matches that of x but the sign matches that of y That is actually kind of what we want But this is all floating point just sign bit to do genetic macro turns on all real floating point types Turn to non zero value value of x is a sign bit set, which is also Not quite what I want. Okay. Well, if the remainder is negative and the Right hand side is positive and flips a sign We want to flip the sign of the remainder if the sign of the remainder and the sign of The right hand side are different so if remainder is negative It's not equal to The right hand side is negative Then we flip the sign All right So we have minus one for the quotient and one for the remainder Which is I believe what we want Okay, and while I remember Shunt this these helper functions up above here so If you want to right shift it Here's double shift So because we now have the helper function you might as well use it And now something fails um That was test z. What is z Shift left So we're using a negative shift. Hang on Z it was shift left. Okay. We're using a positive shift value To mean go left. I bet the overflow Is set The overflow register is set to one if the sign bit of the cumulative has changed during the shift Is that the same as the shifts we're doing here? If the sign bit of the cumulative has changed during the shift the overflow register is set to one So That's not the sign bit Because the sign bit changes depending on how big the thing we're shifting is okay, so Left shift 30 We can't use the same function for both of these So if you want to shift this we have to do that Right and now that passes um, okay, I Think we're probably good. I'm sure there are edge cases here that aren't tested correctly Honestly until I can find the original test suite that was used to verify the real machine Then I Don't really think there's any point, uh Working on this more it is producing what seems to be the right result based on my interpretation of the text But the test suite will be authoritative So Oh, yeah, that was one more thing I wanted to do Which was that I changed the syntax of the Uh Of the assembly, so let's just update the disassembler to match because it's going to work d find double quote Close enough So then this needs to be Uh the opcode Followed by it's always going to be square bracket form Because the actual value will depend The value form only exists in the assembler followed by a address Followed by a comma x suffix So opcode followed by this followed by That's not working. Why isn't that working expected a length Still not working double quote here. All right That's better that looks like Sensible syntax and in fact if I do unassemble Here is the internal dissembler. You can see just ah here. We are actually using the comma x form To dereference a pointer. All right, so there's more stuff that can be tested I've missed a number of opcodes But honestly, I think that's pretty good Uh, oh, yeah, there is, uh One thing Which is We hacked this remember So we actually only want to execute instructions If the cpu is not halted So if we have executed the halt instruction, we just keep spinning on the same Address without actually processing the instruction Waiting for an interrupt to happen So do tin here needs to unset the halted flag And I believe that's finished So this is the obp Don't only what? 880 lines of code. That's quite impressive Not terribly good code. I'm sure this could be optimized more Uh Oh, yes, we haven't done this one. We can't set registers yet. So Value is Sturtole 208 that that actually parses the value So pc is Just finish off the debugger scale register memory protection register age register irq register irqp now the flags dv A x Hang on. There isn't an x. What am I thinking of? So program counter a ex sc slp The two irqs and the flags. So we should be able to say set carry to one Doesn't work. Why can't we do that? Because I didn't enable it in the debugger core Right set carry to one carry is one Set meter one. Yep meter zero PC is four thousand in octal. Yep. Okay. That's good. All right. I'm going to call that done We now have a relatively complete simulator complete with a basic debugger We haven't used break points or watch points yet, but they should be supported for the 1968 era onboard processor as used by a bunch of spacecraft including the Orbiting astronomical observatory It should be really easy to adapt this to simulate the aop the advanced processor The one described in this document. It's got more op codes. No, I think it's slightly more sensible layout This is from When is this from 1973? Uh, I don't know anything about the nssc one the NASA space flight standard computer as used by the Hubble Space Telescope If anyone actually knows any If knows where to get any technical specs, I'd love to get my hands on them and actually do a simulator for it Uh, but it's probably not very different from this All you really need now is a actual spacecraft put it in But uh That's possibly a little bit beyond my abilities to implement That is now obsolete Uh, I would particularly love to get my hands on any original source code including that diagnostics tool Uh, I will have to modify it to make it work with the This cow gold assembler This assembler is very much a quick hack. It Doesn't have any of the linker or paging abilities so You're in real life. You're going to be limited to writing four k words of code but, uh I don't believe there are any other assemblers for it currently. So I still come out ahead It'd be really nice to know whether my simulator Works with real source code Uh, this is all fairly dependency-less C. So it should be possible to rip this out and use it in something else Um, I know the agc simulator is used by the orbiter spaceflight simulator So that you can land a limb on the moon using the original software in the onboard computer Running in an emulated onboard computer It would be rather cool to do this for other spacecraft as well Which use the obp or aop uh, so That is basically it I'm just going to Check this in Push it so that uh, I can't claim I didn't do it Done, uh, so I will write up a I'll put put together a read me with some, uh References and do a blog post, but this is all now on github for people to play with Please play with it And let me know what you think. I hope you enjoy this video Please let me know. Please. Let me know what you think in the comments