 Greetings, RISC-5, friends. So last video, we talked about transparent latch and coding it up in MyGen and formally verifying it. So now we have a nice transparent latch that we can use throughout our model. This video, we're going to talk about asynchronous memory. But first, a little correction. I mentioned that I was working with RV32E, which is the embedded version of the 32-bit RISC-5 instruction set architecture. Several people wrote in to say, no, that's probably wrong, because RV32E only has 16 32-bit registers and not 32. And they are indeed correct. What I meant to say was RV32I, which is the base or integer extension of RV32, which does indeed have 32 32-bit registers. So thank you very much for pointing that out. It's RV32I. A lot of other people wrote in with some interesting suggestions. One of the most common comment was, why didn't you do this versus that? Or you could do this versus that? Or your buses don't have to be that big? And really, the reason is it all comes down to a trade-off between hardware and efficiency, I guess. In some cases, the less efficient you make your software, the less hardware you have to use. Or sometimes the more hardware you have to use. Like, for example, I have three 32-bit buses so far, X, Y, and Z. Now you could get away with a single 32-bit bus and just time slice it. So you send, say, RS1 on the bus first. On the next cycle, you send RS2. You have to latch those. And then on the next cycle, you do the calculation and send the result on the data bus. So that's already three cycles. So I really wanted to have, as much as possible, single cycle instructions. And in some cases, that will result in a larger bus, but maybe less hardware. So again, it all comes down to a trade-off. And this is just the trade-off that I've decided to live with. So there is that. So why don't we get started and talk about asynchronous memory? So let's take a look at a typical memory. So here is a page of the datasheet that I have for a typical memory. Now this is a static RAM. It's an SRAM, which means that there aren't any refresh cycles. It's also asynchronous, which means that there aren't any clocks. So as you can see here in the read cycle, number one, what we're assuming is that the output enable is active. So let me just draw really quickly a picture of the signals for the memory. So there's a data bus. It's bi-directional. There is an address bus that, of course, goes in. There's a write enable pin, which is active low. And there's an output enable pin, which is also active low. So there's also a chip enable pin, which we're just going to assume is always enabled. So for this read cycle, basically the output enable over here is always active. So we're always outputting data. And what that means is that we're just continually reading the memory at whatever address we specify. So according to the data sheet right here, we can see that when you present a new address, let me color that in red, when you present a new address here, then it can take as much as this amount of time in order for the data to become valid. So you present an address here, and then you can read the data at that address over here. And in the meantime, the data is pretty much meaningless, so it could be anything. Now, I think I have on the side here, yeah. So here's some timing. Here is the address access time, which for a 12-nanosecond access part is a maximum of 12 nanoseconds, which kind of makes sense. Same thing for the 8-nanosecond part and the 10-nanosecond part. So if I could get a grip here. So that's really what's going on with address and data. So we can also look at the write cycle. So there are many different ways that you can do a write. And the data sheet calls these out here. There's write cycle number one and write cycle number two. For write cycle number one, it basically says it's chip-enabled-controlled, which we're not going to do, because we want the chip to be enabled all the time, just for simplicity. So let's take a look at write cycle number two. This is write-enabled-controlled, and the output enable is high during the write cycle. So the output is basically disabled. Now, the thing about the bidirectional data bus is that in the memory, there is effectively two buffers, one going out and one going in. And they're sort of connected at the ends. So the idea is that when you want to read from the memory, data flows through this buffer. And that's what the output enable line is for. And when you want to write data, data will flow through this buffer into the chip. And that's what write enable is for. So the thing is that you may not want to have both of these buffers enabled at the same time. Now, the chip is able to handle that. In fact, by default, if both of these are active, then it's only the write buffer that's active. So that sort of takes precedence. But let's suppose you didn't do that, because your output enable signal maybe went to other parts of the circuit. So let's take a look at what happens when you want to write. So what it basically says is, OK, you put the write address onto the address lines. So write address goes here first. But you don't put the right data in there, because you're still outputting from the chip. And of course, if the chip is outputting and you're trying to drive data into it, that's no good. Because then you'll get a driver-driver conflict and burn something out. So what they do is they also say, OK, turn output enable so that it's disabled. So we disable that as that's step one, that's step two. Now, it basically shows here that, let's see, the data out goes high impedance. Let's see, when does it go high impedance? It actually goes high impedance after you turn the write enable on. So you enable write enable as the third step. As the fourth step, you basically wait. And how much time is that? Well, it says that it's T-H-Z-W-E. And we can take a look at what that is. Let me just scroll over. So we're looking for T-H-Z-W-E. And that says that for a 12 nanosecond part, this is a maximum of six nanoseconds. So you can't present data within six nanoseconds because otherwise the memory buffers may not be fully turned off. So once you wait that amount of time, then you can put the data in. So step five, of course, would be put your data in. And let's see. Let's see. OK. And then it says to disable the write. So six is disable write. And what it says here is that you have to hold the data input. You have to hold it for T-H-D. Now let's take a look at what T-H-D is. T-H-D is right over here, and that says it's a minimum of zero nanoseconds. So you could stop presenting your data the moment you disable write. Of course, the problem is that when you do that, well, which actually comes first, if you get it the wrong way, then your hold time is actually negative, which is no good. In other words, you release the data before you release the write enable. And that's no good. So just to be safe, you want to keep the data input stable while your write enable pulse is transitioning. So let me draw that out in terms of sort of a diagram here. So here is your write address. So we turn that. So that's your write address over here. So the second thing you do is you disable write. So the second thing you do is you disable the output enable. And you could do that simultaneously with presenting the address because if the chip is in read mode, then all it's going to do is it's going to read that address. So we're going to disable output enable. And then there is write enable, which we are going to enable. So and then you have to wait however long this is for the buffer to turn off. Then you can present the data. This is the write data. So there is where you present the data. And then while you're presenting the data, you can release the write enable. Then you can release the write data and release output enable at pretty much the same time. So that's write cycle number two. Fairly complicated. But there is another write cycle that you can do instead of this one. So this one is write cycle number three, which basically says that the output enable is always low. So it's always active. So this is what I meant when I said that you can have the output enable and the write enable both active and write enable will sort of take precedence. So let's go ahead and erase these steps over here and erase this whole thing. So again, the first thing that you do is you present the write address to the address bus. So that's step one. Now the output enable is always active. So we're not going to change that. So the next thing that we're going to do is we are going to activate write enable. So make this active. That's step number two. Step number three, of course, again, is to wait for the buffers to turn off for the memory. So three, wait. And we know that that's going to be 10 nanoseconds, not milliseconds, 10 nanoseconds. And then at that time, you can present the data in. So four, data in. Okay. And then again, you release the write enable and then you can release the data after that as well as the address. So basically it's going to look like this. This is your write address over here. That's your write address. Your output enable is always active. So whether you're reading or writing the memory, you'll leave it alone. Okay. So the write enable now goes low and you have to wait some amount of time. Then you can present the write data. Whoops, that's wrong. Then you can present the write data after that and then you can release the write and you sort of want to keep the write data around for a little while longer. The problem though is that the moment you release the write, well, output enable is going to turn on again, which means that your chip is going to be outputting data. And again, if you don't release your write data fast enough and that's this time over here, then you're going to get a driver-driver conflict. So you have to release this pretty quickly. How fast is that? Well, it's LZWE. Let's take a look at what that is. That's over here. LZWE is a minimum of three nanoseconds. Interestingly, they don't say what the maximum time is. So, okay. So that's write cycle number three. Now, I don't really want to do that. I want to do write cycle number two, which is where you disable the output buffers from the memory and then you can write to the memory. So this way what I can do is instead of this, again, this is going to be write cycle number two. So it's this amount of time that I can actually wait. And once I do that, I can present the write data like that. So that's pretty much what we want to implement in code. So why don't we just go ahead and do that? So here's the code. So the interface is basically just, you know, the address. Data in and data out. Again, because nMyGen can't really do bidirectional signals. We just have separate data in and data out. And, you know, we read and write them appropriately. We also have an active low output enable signal and an active low write signal, which, you know, we'll do it active low because that sort of corresponds to the actual chips that we'll be using. So in the constructor, what I've done, just to be a little bit flexible is I've added some parameters. So you can specify the bit width of the memory and the number of address lines. So the idea is that for, you know, quick formal verification of the entire system, we're just going to set the width at 32 bits and the address lines to five. The reality is, of course, that the memory chips will probably end up using our 16 bits wide. And of course, they'll have way more address lines than five, but we can ignore those address lines because they'll always be set to zero. So that when we formally verify the system with modules that represent the actual chips that we're going to use, you know, we can just set the appropriate width and number of address lines. Of course, we have some assertions. Obviously, you want the width to be greater than zero, the number of address lines to be greater than zero, and I really don't think that we're going to need any more address lines than 16 at any point. Okay, so here are the signals themselves address and active low output enable and write and the data in and data out. Now, here I have the actual memory. It's basically an array put together using a Python list comprehension. So, you know, two to the whatever address lines you have, that's how many memory cells you have. And I set them as recetless equals true, which basically means that even though they will be clocked using the write signal, there is no separate like reset line. So, you know, the memory powers up and whatever data it has is whatever data it has. There's no zeroing out of memory. All right. In addition, rather than putting this memory as a local variable inside elaborate, I decided to break it out as a private attribute of the class. And the reason is that for formal verification, I do want to peek inside the memory to make sure that the internal state is correct. Now, this is different from like unit testing where typically you only test the public API. However, it is very convenient to look at the internal state and especially for formal verification that becomes fairly important. You probably get around it by, you know, doing things like manipulating the API to read the memory that you're interested in rather than directly looking at the memory. But, eh, okay. So, elaborate where we're building the logic of the module. It's fairly simple. So, the thing is that because we don't actually have a global clock that we're looking at, we're only looking at the local write signal, we want to sort of turn that into a clock so that we can clock data into the memory when the negative write signal goes from low to high. So, to do that, we create a clock domain. We're going to call it write clock and we're going to say that it is a local clock, which means that only this module and any of its sub-modules have access to that clock. No higher module has access to this clock, so it's kind of, you know, a private clock almost. So, we add that to the domains, to the clock domains, and then we set the clock signal because remember a clock domain has a clock signal and a reset signal. So, we set the clock signal to negative write because that's what it's going to be. Okay, so now here what we're doing is we're always outputting zero on data out. Now, in nMyGen, again, previous state, later statements take precedence over previous statements. So, I can set data out equal to zero and then I can set data out equal to one and data out will be equal to one. So, this is sort of a default value, zero. And then finally, if the output is active and the write signal is inactive, then the data out is simply going to be whatever the data is in the memory of the address that we've selected. So, and it's in the combinatorial domain because this is an asynchronous chip. You change the address, the output changes. Now, again, we can't simulate things like delays. I mean, we probably could by having maybe a clock that goes at, I don't know, 0.1 nanosecond cycle time and then saying that this signal happens 100 cycles after that signal. But that is probably not a great idea and I think abstracting out the delays is just fine. This last statement is where we actually clock the data into the memory. So, if there is a write signal and it goes high, then we clock the data in into the address. This was just a quick simulation to make sure that I was doing the right thing. And in future modules, I don't think I'll have to simulate anything because at that point, I'll sort of like, you know, be in the groove and sort of understand exactly what I'm writing. But it's always a good idea when you're first starting out with nMyGen to write some simulation to actually see the signals do what you wanted them to do. Okay, formal verification, how does that work? So, the first thing we're going to do is create an async memory with 32 bits and five address lines because that is what I'm going to be using in the system. I could probably get away with way fewer bits and way fewer address lines because after all, if it works for 32 bits, it probably works for two bits. If it works for five address lines, it probably works for two address lines. But nevertheless, I'm just going to do this anyway. Okay, so now when you pass signals to formal verification and you basically tell it, you know, you can manipulate these signals as you like. You can turn them high. You can turn them low at any time, whatever. You can restrict the things that formal verification does if you know that you're going to be using the inputs in a particular way, and that's what these assume statements do. So, first of all, what we're going to be assuming is that we're not going to be output... we're not going to be outputting at the same time that we're writing. So, this basically says that we're not using that right cycle where the output enable is always enabled and then we just write. So, both of these cannot be zero. Okay, the next thing is how we're going to specify that the output enable and the write signal changes have to be separated by one cycle. So, if you remember the clock diagram, we don't want to turn off the output and then immediately turn on the input to the memory because, you know, there may be some buffer collision, so this is just giving us some space. So, if the negative output enable fell, in other words, we're turning on the output, then assume that currently, of course, the write is going to be disabled. Honestly, I probably didn't have to specify that because of the above assumption, but currently the write is going to be disabled and also in the past cycle, the write is going to be disabled. And the same thing holds true with the write signal that if the write signal becomes active, then we want to make sure that currently the output is not active and also in the past cycle, the output is not active. So, this forces formal verification to separate when we change the output and write signals. So, this next statement means that the way we're going to use the write signal is that when the write signal goes high or when the write signal is, you know, active, we're going to assume that the memory address is stable because, again, you know, we don't plan to change the address when we're writing because that would be pretty bad. So, what we're saying to the formal verification engine is don't change the address if you're going to write. Okay, so here's a cover statement. We just want to make sure that, you know, at some point we're outputting A's and in the previous cycle we're outputting B's. Now, of course, because of that separation between reads and writes, you can't write A's, read A's, write B's, and read B's using this cover statement. What this cover statement says is read A and then read B. So, what we expect is that formal verification is going to say, okay, in order to do that, we have to write A, write B, and then read A and read B. Or, you know, write B and write A. It doesn't matter what you do. So, that's the cover statement. And now we get to the actual assertions. So, we want to make sure that when the output is disabled we are outputting a zero. Otherwise, if the output is not disabled then we want to make sure that the output is equal to the memory cell corresponding to the address. In addition, when the write signal goes high, we want to make sure that whatever we wrote is actually in the memory cell. Let's see. So, the next thing, this is something that I stole from ZipCPU. How to formally verify a memory. Basically, what you do is you pick an address in the memory and you say, well, if we don't write that address then the contents of that address should not change. If we do write that address, then of course the contents of that address should change to what we wrote. And that's basically what all the rest of the code is going to do. What we do is we sort of reproduce that local clock because we want to know when formal verification decides to write that memory cell. So, that's all this is doing. And then we have saved data in order to sort of keep a kind of a synchronized copy of the data that we think should be in the address. So, what we're going to be doing here is all the time if the address is the same as the address that we picked and what any const means is that formal verification is free to pick an address, any address, but it has to remain the same throughout that session of formal verification. And of course the magical satisfiability solver, the formal verification engine is going to say, well, if it works for one address then it should work for all the addresses and it's going to attempt to prove that. But what this says is we're going to fix it and then treat it as fixed for the rest of the session. So, if the address is equal to that address that we picked, then if we're saving the data then put the data in into our copy of the saved data. So, that just says when we're writing the memory, keep around a copy of that memory. The next thing is when initial, that is basically the very first cycle of formal verification. Now, on the first cycle we actually don't know what's in memory. So, we don't have a saved copy of that. So, what we're doing is we're assuming that the saved data is equal to whatever is in memory. Now, formal verification is free to say, well, saved data is zero in the beginning, so I should zero out the memory, or it's free to say, I can put whatever random data I want in saved data as long as it's the same as whatever is in memory. And finally, we also want to assume that we're not doing a write at the beginning. So, we're just going to assume that write is disabled. And if we're not in initial, then the saved data has to match the memory. That's all there is to it. So, we can take a look at formal verification now. So, let's go ahead and compile the code. Great. And let's go ahead and run formal verification. Okay, so formal verification took, you know, about a minute and a half or so, all totaled. And we can see that bounded model checking past, which is great. In addition, we found our cover statement and that passed. So, let's take a look at the trace that it came up with. So, we can take a look at the memory. And we can see what formal verification did in order to read b's first, and then read a's. So, what it did was it set the address to zero and the data into b's, and then it wrote right over here. And then it set the address to two and the data into a's, and then it wrote it right over here. And then it read zero, and then it read address two, which totally makes sense and sort of, you know, verifies that what we think it does, it actually does. Okay, so that's great. We formally verified the transparent latch and we formally verified the async memory, which means that we have all the pieces we need to create a register card. So, let's do that. Okay, let's take a look at the register card now. So, here is a kind of a cleaned up diagram of the register card. So, there are really well, let's see one, two, three, four major components here, aside from the clocks. So, there are the memory banks that's going to store the data. There are the output latches which are going to latch the X and Y outputs. There are the right buffers over here, which will put the Z bus onto the data inputs of the memory when we're going to write them. And then there are finally these multiplexers which select the address that we're reading and writing. And again, remember that we have a phase one clock that we have a phase one clock, which tells us whether we're doing reading or writing and they're interleaved. And then a phase two clock, which is when we're writing, when we do the actual write. So, I've sort of drawn out all of the control signals over here. So, the control signals are the ones in red. So, these are the ones that are going to have to be on our system wide bus. So, there is a bit which says whether we are outputting a register to the X bus or register to the Y bus. And then another bit up here whether we're writing the Z bus into a register. And then of course we have the corresponding register numbers, the X reg, the Y reg and the Z reg. So, we have those and we have the clocks and that's really about it. And of course, the X, Y and Z data buses. So, we can see from this big complicated diagram that what's actually going to happen is during phase one, so right over here we are going to read which means that we are going to put whatever address we're going to read from into the X or Y memories. So, here is the read address which is going to be the X or the Y register. And then on the right half of the cycle we're going to give it the right address which is the Z register. So, we're going to do that all the time regardless of whether we're reading or writing. Now, controlling the latch output is the bit which tells us whether we're outputting the register to X or outputting the register to Y. So, that controls the latch. In addition the latch is latched on the negative edge of phase one. So, that's when we latch the output of the memory onto the bus. And when we're writing, well, there's this little circuit over here which basically controls the right buffers over here and it also controls the right pulse to the memory which basically gives us this when we're writing that there is the right data right over here to write into the memory. And that's really about it. So, you can pause and look at this diagram and, you know, prove to yourself that it works, but let's not just leave the proof up to you or me, let's leave the proof up to formal verification. So, let's take a look at the code and see what it looks like. Okay, let's take a look at implementing the register card. So, up there I've put the diagram of what we wanted to build and I'll just be going through the code and, you know, drawing on that diagram to show exactly what piece of code implements what piece of that diagram. So, I just put some comments showing the attributes. So, we've got our three buses, X, Y, and Z. We have our control lines. These are one-bit control lines, whether we're reading from a register to X, reading from a register to Y, or writing from Z to a register. And Reg X, Reg Y, and Reg Z, those are which registers we are reading or writing to. And here's a little diagram of the clocks and it's, you know, basically just taken straight from the diagram. So, these are the clocks that we're going to assume that we have to work with. So, these are basically two clock domains, pH 1 and pH 2. Alright, so here what I've done, well, of course, up here I've declared the buses as 32 bits. I've declared these control lines as single bits and the registers as five bits because, of course, there are 32 of them. And then I've defined the sub-modules. Now, the reason that I put them in self, as opposed to you know, local variables inside elaborate, of course, again, is because during formal verification, I think during formal verification, I might want to look at those signals, so. So, we've got two asynchronous memories. Those are the two banks. Those are this XMEM and this YMEM bank. We have the two latches. So, those would be this latch here and this latch here. And then we have the what I'm calling the right latches, which are these two buffers over here. Now, again, I'm using a latch. I'm not actually using the latching feature. I'm just using it for its buffering. And in fact, when we implement it, I'm probably just going to use the same chips because it's always easier to design a circuit with the same chips because then you don't have to have so many different chips to place. You can just place the same chip in all sorts of locations. It's the same thing with resistors. You probably want to stick with using the same values of resistors, the same values of capacitors, unless you can't. And then, of course, you don't. Okay, so here's the code. We start with a module and we start by pulling out phase one and phase two, specifically the clock signals for those domains. Remember that a clock domain has a clock signal and a reset signal. So, we're going to just be using the clock signals. And here, what I've done is I've just, you know, given myself saved myself some typing. So, here I am loading the sub modules into this module. And again, you know, here I'm just sort of, this is not saving myself typing, but this is actually for readability. So, you know, basically I'm saying that the read phase is when phase one is high and the write phase is when phase one is low. So, the next thing we have is just a statement that says that the two write lines for the two banks are tied together. So, we're reading them at the same time. So, I can put in green, sort of. That's what that statement does. And let's see what we have next. Okay, so, again for readability, I say that the banks output enable signal is equal to the read phase. So, in other words, during read, we want to output from the banks. Now, because the implementation has an active low output enable signal that's why I invert it over here. So, that just says that we have for the read phase, let's see here it is. Right here and right here. So, those are those signals. Okay, so the next thing is I have two addresses. Now, these are actually intermediate signals. And MyGen already has a MUX statement. And what this allows us to do is select one of two signals based on a third signal. It's kind of like, you know, if then else, or maybe a ternary. So, what this is, so what this is is the implementation of these multiplexers over here. These two multiplexers. Based on the read phase. Right, so we're selecting either X or Z or Y or Z. And then we are feeding that into the memory address. So, that would be these lines over here. Great. Alright, what's next? We have some data lines. Okay, so what this is. Now, this is basically the way that you define a bus that has high impedance drivers on it. And only one of them is going to be driven at a time. So, we can see that this is this mem data bus. So, I'll draw that here. That's what these buses are. Mem data, X mem data, and Y mem data. So, the X mem data is just equal to the data out coming from the bank and the data out coming from the right latch. So, basically this bus gets driven by either the output of the memory or the output of the latches. So, that's what that implements. And remember that when an output goes high impedance, it is zeros, which means that, of course, OR is the right thing to do. And finally, of course, we want to take that bus and feed it into the memories as well. Okay, and this section of code I just realized that I completely skipped over. So, let me take care of that right now. This is the multiplexer right over here. So, we have the negative right signal that's normally one. So, that's this part of the multiplexer right over here, except if Z to reg is one, then we select phase two. Now, I didn't use this using the MUX statement, but I could have. But to me, this was sort of a little easier for me to understand that the negative right is inact, the right signal is inact... that the right signal is inactive and only when Z to reg is selected or high that we let through phase two. Okay, and now I'm basically saying that the output enable, again, this is a readability thing, for the output enable, which I'm saying is a positive signal, it's the inverse of negative right, and then of course I'm just inverting it again, because the output is a negative enable. So, you know, this is because sometimes active low signals are hard to reason about, so it sometimes makes sense to make all of your signals positive or active high, and then you can reason about them. So, that's what this does, it basically just sets up the latch. So, we have data in coming from Z, so that's this okay, we have latch enable is always high, because it's always going to be transparent, it's always on, and the negative output enable is of course the negative of the latch output enable, which is just this signal over here. So, that's what that does. Finally, we've got the X latch and the Y latch to deal with, so the data in comes from these memory boxes, so that's here. The latch enables are just phase one, so that's this and this, and then finally the output enables are just the negative of whether we want to read from those registers. So, that's this signal here and this signal here. And the very last part is to actually go ahead and output the latch is, and the final part is to output the latch is onto the buses, which would be this and this, so let's see, yeah that pretty much covers the entire design. So, now let's take a look at formal. So, in order to verify this, what I've done is remember last time we sort of assumed we wrote assume statements for the various signals, well here what I'm saying is I know what the clock is going to look like, so I'm just going to create that clock. So, I create two clock domains, phase one and phase two. Of course, I'm going to declare the register card and I'm going to add the clock domains to the modules domains and the submodules to the submodules as usual. I have a cycle count, which I actually don't use for anything, and a phase count. So, the idea is that we are going to have six phases. So, let me see if I can move over this diagram. Yeah. So, we can see the six phases here. It's actually one, two, three, four, five, six, and then back to one again. So, those are the six phases. I mean really I could have just said there were four phases and maybe saved myself some time, but it looked more symmetrical to just divide the clock into six and then, you know, change the signals based on that. So, that's what this section of the code does. We're basically counting the phase up from zero to five and then back to zero again and we're manipulating the phase one and phase two clocks. We're also increasing the cycle count every time we hit the end of a phase. Again, I don't really use it, but, you know, why not, it's there. Now, here is where I have some assumptions, because remember that when you allow formal verification to take control of a signal, sometimes you have to tell it that there are certain restrictions on the signal. And in this case, what I'm saying is that all of the control lines and the data on the Z bus must remain stable or we're stating that they will remain stable throughout an entire cycle. So, you know, because otherwise formal verification is just going to start, you know, flipping them up and down in the middle of a cycle. And we state that we are not going to be doing that. Okay, so here I just have a simple cover statement. What I want to know is how do you manipulate the control lines such that at some point the output on the X bus is non-zero, the output from the Y bus is non-zero and they are also not equal to each other. So what I want to know is, you know, kind of what I did with the asynchronous memory. Can you show me an instance where the two buses are reading from two separate registers and they're both non-zero? Which reminds me, once again, I've forgotten to say that register zero's output is always zero. So I will add that after I do this formal verification. I promise. In fact, I'm going to put it to do here right now. Because I've consistently been forgetting that, haven't I? Okay. So here are some assertions. Now, basically, this is that when you read X, when you read a register into X or you read a register into Y, those buses should not change during the cycle. So, you know, that basically says that they have to remain stable so that we can do the calculations through the entire cycle. In addition, if we don't want to output anything onto the buses, then those buses should be zero. High impedance in the actual circuit zero here. Now, on a right, what we want to make sure is that the data that we have on the Z bus was actually written to the register that we wanted. And not only that, but it's written to both memory banks. And here's where we sort of sneak into the private instance variables. So here's where we sneak into the private attributes of the register card and, you know, peek into the memory and just make sure that, you know, everything is correct. Okay. And then here is pretty much the same thing that we did with the asynchronous memory. We are going to just pick an address, any address, and of course I'm going to change this when the address is zero. And make sure that the contents of that address doesn't change unless that address is written to. So that's what all of this stuff is. It's basically the same thing. Except in this case, we want to make sure that we check both banks of memory because, so remember that each bank of memory holds a copy of the registers and, of course, they should be the same. And that's it. So formal verification has completed. It took a little while. It took about half an hour to do, presumably because there are more things to check. I don't know. It's kind of no coincidence that it goes very quickly for maybe, you know, the first 12 cycles and then it just bogs down just that sort of interface between going from cycle 11 to cycle 12 probably because we have six phases in our cycle and it's only, you know, at the interfaces of the cycle that you can change the control signal. So, you know, maybe that has something to do with it. So it happens when you're checking the assertions for step 11, it happens again when you're checking the assertions for 17, it could be, you know, three cycles down the line. But, you know, whatever, the point is that it passed, which is pretty good. If we look at our cover statement, it didn't take very long to find an example. Let's actually take a look at that. Okay, so let's take a look at is this the reg card? I think this is the reg card. So we can take a look at the buses. We can take a look at phase one and phase two. Okay, so we can see that it's gone through two phases. Let's take a look at the control lines for x and y. Okay, so they are both zero, except on the very last cycle where it changes to one. We can look at reg x, reg y, and reg z. And we can look at, there should be another z thing to register. Okay, so we can actually see that what Formal decided to do is right over here, we are writing to register number five. What are we writing? We're writing 08,000, and blah, blah, blah. And then on the next cycle we are writing to register one with 0,00 2,0,0,0. And then over here we are going to read from registers five and registers one simultaneously. And that's how you get on the data bus two different values that are non-zero. So that's pretty cool. So it sort of shows that our circuit at least can do something, which I guess is important. So now what I'm going to do is I'm going to modify the code to add register zero. So there are a few ways we can go about this, but almost certainly what we're going to need is something that checks whether the X register or the Y register register number that we want is zero. So let's just add I'll just call that some zero checking thing, right, and Y goes in here and X goes in here. And then maybe what we want to do is if it's zero we don't want to read from the memory, but instead what we want to do is sort of inject a zero. So maybe what that should be is perhaps another latch, another two latches which go to X and Y. No, that's not really going to work because then we have to turn off the other latches. How about this? How about we have a buffer that goes into here that outputs a zero and if the register is zero and we're reading it then we then we select that buffer to the output instead of the memory buffer. Yeah. So maybe that's the right way. So we're going to have to modify these output-enable signals. So let's do that. Well, let's see what changes that I made. So first of all I added these zero latches. So this is going to put zeros onto that internal data bus if the register that we're reading is zero. So I of course added that to the sub modules and then we have the checks for register zero. So this is just a signal and if the register is zero then the signal is high. So the modification that I had to make of course is that when we read memory we don't want to read the memory if the register is zero because otherwise we're both outputting a zero through the zero buffers or the zero latches and through the memory. Which wouldn't have any effect on the simulation but of course it or on formal verification but it does in the implementation because then you'd still be outputting something on the memory. By the way, are these rings in my glasses distracting? So I have a ring light in the camera that is giving me some extra light. Let me know. So what else did I have to do? Well of course I had to set up those latches right over here. So the latches have data in of zero. They're always transparent but the outputs are only enabled when we're reading register zero for x or register zero for y. Let's see. And then this is the modification to the internal data bus. I just added that or statement. Let's see. That is it for the logic in terms of formal verification. So first of all I modified this assumption. Originally I had it so that if the phase count was equal to then the current in the previous five control signals all had to be stable. This time I decided to make it so that if the phase count was non-zero then all the control signals had to be stable. Just for the previous clock cycle. Turns out that this saved a lot of time in formal verification. So that was interesting. And let's see. I had added a new assertion that if we are reading from a register and that register number is zero then the output should always be zero. And finally when we're actually doing the checking of the memory I am placing an assumption saying that you can pick any address except zero. You can't pick that one because you can't write to it. And of course obviously even if we do write to it it doesn't matter because we're not going to be reading from the memory at register zero. So that was that. And here is the command line. Let's get rid of the whiteboard. And it passed. I mean it did the right thing. And you can see that it only took 11 minutes instead of something like what was it half an hour or more. So that is the register card. And that is it I think for this video. So we have completed and formally verified one card out of the system. We have determined that in addition to the X, Y and Z data buses we are going to need certain control lines for the registers that we're reading and writing and whether we are reading and writing some registers. So those are our going to go into our control bus. So the next video we're going to deal with another card. The shifter is probably pretty easy to do. Why don't we do that next video? So until then, thanks for watching. See ya.