 Greetings, Verilog friends. If you've written any amount of Verilog, you've wondered how you can test that your Verilog actually works. And typically you're going to write some unit tests to stimulate your module and see that the output is as you expected. So the only problem with this is that it does rely on you picking your unit tests extremely carefully and assuming that those unit tests are characteristic of the module and that you don't have to write any more unit tests. So unit tests are great because it will tell you whether your module does what you think it does in a certain way. And when you change your module, you can rerun your unit tests to see if there are any regressions. But there is another method which is maybe a little more rigorous and it's called formal verification. And the idea here is that you specify what properties your module must have. And then you run it through basically a magical mathematical filter called a satisfiability solver. And what that does is it tries to disprove that those properties hold at any particular point using any particular input. You basically assert that this property is absolutely true. And then the solver tries to prove that that's false. And if it can't, then you've got a pretty good mathematical proof that your module is correct with respect to those properties. So of course you have to pick those properties well, but it's a little more rigorous than unit testing. Let's talk about how to get started. Now I'm going to specifically talk about YOSIS and Symbiosis because those are both open source and free. In addition, they're convenient because they will take your verilog code and compile it to certain FPGAs, some lattice FPGAs, a few Xilinx FPGAs and there are a few others in that mix, but it's all free and it's all very fast. So I have a simple one pager here and you can get the URL for this down below. So if you're on Linux or OS 10, this will be extremely easy to follow. All you have to do is open up a terminal or a command window and do the instructions that are in this document and you're done. If you're on Windows, it's a little bit more difficult. I'm not going to be using Windows, I'm actually going to be using the Windows subsystem for Linux because now I'm on Linux basically. If you don't know what Windows subsystem for Linux is, it's basically a Linux shell inside of Windows and you would be well advised to make sure that the Windows subsystem for Linux is working on your system. So there are a few formal verification modes that I'd like to talk about. One is called the bounded model checker. The other is called prove or the prover and the third is called cover. So let's talk about bounded model checking. So let's suppose you had a simple binary counter that goes from zero to one to two to three to two to three and then back to zero. So you can probably think that in order to verify that the counter actually works, you set it to some state and then you clock it and then it should go to the next state and the next state and the next state and the next state. So let's say that if you are in state zero, then the next state should be one and if you're in state one, then the next stage should be two and from two you should go to three and from three you should go to zero. So it's also pretty clear that some of the disallowed transitions would be from zero to two or from two to zero or from one to three, from two to one, you don't wanna go backwards and on and on and on. So if we consider the current state as N and the previous state as M, then you can see that we have a bunch of state transitions. So for example, this is the entire state. So you have state zero and one, that goes to state one and two, that goes to state two and three, that goes to state three and zero and then it goes back to here and there's a bunch of other illegal states like zero to two or two to zero or two to one or one to three and there's a whole bunch of illegal states that surround this valid bunch of states. So these are all the valid states, valid and outside, you would have all the invalid states. And the idea behind bounded model checking is that given starting from a valid state, you remain within the valid states, then you've checked your model for being bounded. In other words, in other words, given an initial state and clocking it for a certain number of states, you never enter the invalid state space. So it's pretty clear that the number of state transitions you check is pretty important because of course, if you had say a 64 bit counter, well, and you start from zero, you would want to check two to the 64 states to make sure that you remain within your valid state space. So we're going to start off with a simple combinatorial module. There's no clocking, nothing like that. So I started off with some simple skeleton code. Basically what I like to do is I like to protect my Veralog module using this standard sort of include protection so that if you have other modules that also include simple.v, then this module will only be included once. Default net type to none because when you define your nets, it could be either a wire or a register and if you forget to do that, then this will complain. And then this is just a useful time scale for simulation and traces. So I'm just going to call this a simple module and let's suppose I want to make a simple adder, right? So I'm going to have an input and I'm going to call it logic. Usually you say wire or register but in the latest versions of YOSIS and Icarus Veralog, you're allowed to say logic. And the reason for that is that it was kind of confusing when you're supposed to use wire and when you're supposed to use reg. So the powers that be decided that, well, let's just say logic and it's up to the tool to figure it out. So and I'm going to make it a 64 bit adder. So we'll just call this, I don't know, A and we have also B and the output is also 64 bit and we'll call it S or maybe we'll just call it Y. So and actually I said this was going to be an adder. Let's make it a subtractor, okay? So since this is combinatorial, I can just say assign Y equals A minus B but let's implement it using two's complement. Let's suppose, you know, I didn't want to use subtractors for whatever reason. So I'm going to do A plus, now the two's complement of B is simply the inverse of B or the negation of B plus one, okay? So this is subtract Y equals A minus B. Now this should implement subtraction but how can we prove it? Well, I mean, if we were to do unit cases, unit tests, we would check a few values of A and B and check the output and then if those few unit tests seem to work, then we declare that the module works but of course that's not exhaustive. So let's go to our formal module. Now usually what you can do is you can include your formal assertions into the module that you're testing and protect them using this. So here's where your assertions go here. I don't typically do that because I sort of like to just stick with the public API. I don't really want to test any internal state. You can, but I'm not going to at this point. Okay, so here's a skeleton for my simple formal module. I declare the module up here. I'm including the module that I want to test and I'm including an instance of the module. So what you do is you take your formal module and you copy over the API for the module that you want to test. So these are the things that the formal verification will be able to use. Now here where I instantiate my symbol module, I'm just going to copy the signals. You don't have to do this. I am aware that there is a simple way to simply say all of the named signals are the same as they're named in your module but I typically don't like to do that because one of the names is different. So anyway, so now I have the module instantiated and now I am going to begin my formal test. So what I'm going to do is I'm going to say always, if any signal changes, I'm going to assert that Y is equal to A plus A minus B. Now the syntax checker is going to complain about this and this is unfortunately because IVeralog doesn't understand assert statements so we'll just accept this as it is. Now notice that what I'm doing is I'm implementing subtraction one way and I'm testing it a different way and this is important because if you tested it in exactly the same way as you coded it then all you'll be doing is testing whether you can copy and paste code properly which is not what you want to test. You basically want to assert that some property about your module exists even though your module doesn't explicitly implement that property. Okay, so now we have to define a configuration file. So under tasks we're just going to do bounded model checking for now. The option specifically for the bounded model checking task is that the mode has to be bounded model checking. This is just the way that symbiosis works. This depth doesn't actually matter because I don't really have clocks but we're just going to keep it that way. Engines, I'm just going to use the default of SMT BMC. There are other engines that you can use but I'll just stick with this. Doesn't really matter at this point. So the script is you want to read in formal mode and when you read something in formal mode it defines that formal define in system verilog syntax simple formal dot v. This is your top level file and then you want to prep as the top level module simple formal. So there's my top level module. And finally you just list out the files that need to be included in the compilation and in this case it's just going to be simple formal and simple. Okay, so now let's run it. Okay, so now let's run this. We do have to run SBY under Windows as sudo because unfortunately SBY, which is a Python program attempts to set resource limits and apparently in the Windows subsystem for Linux you can't set resource limits unless you are sudo. So let's just run this and see what happens. Okay, and if we look down at the bottom we can see done. So what this does and you can sort of see in the logs that it's checking assumptions and assertions and step zero, step one, step two and so on all the way up to step nine. Of course these steps are all going to be the same because there is no clock really. So what this has done is it's basically said in all cases this is mathematically equivalent to this in every single case. So basically I've checked all cases of A and B against Y. So I didn't have to do this exhaustively but this was proven mathematically. Now you can imagine that you have some more complicated logic and you can do that. So let's suppose I made a mistake in my module and I didn't add one. Let's suppose I just did that by mistake. Now let's see what happens if I try to prove that this works. Well it says fail and when it fails it will give you a trace. So that's right here, engine sub zero trace.vcd and we can run GTK wave which is a nice viewer for VCD wave forms and the directory it puts it in is let's see simple formal BMC. So simple formal BMC, engine zero trace VCD. So it basically is saying your assumption is false and here is a trace that shows where your assertion went bad. So let's pull these out. So what I did was I just selected my simple formal module. It tells me all the signals in simple formal module. I'm clicking on the first one, shift clicking on the last one and then I'm gonna hit append and indeed A and B and there's Y. So obviously I did something wrong and then I would have to go back and try to debug Y. So let's set this back to one and then we can run it again and we're done. Let's talk about the cover mode. Now in cover mode what we want to do is find out if a given state can actually be reached from your initial state. So let's suppose you wanna cover this case and you wanna find out is it actually possible to get into that state from say this state and you also give it a certain number of transitions to go through in this case, you can have a minimum of three transitions because obviously in two transitions you can't get into the state three comma zero. So what cover allows you to do is say here's a situation I want to find out. So this is kind of useful to say I want my module to end up in this state. How do I get into this state? Now cover is also interesting to use in this case. What we can do is we can add a cover statement. So cover the case where Y is equal to, there's 64 bits of hex, zero, zero, zero, zero, A, B, C, D, E, F, nine, nine, well, here let's just make it simple, right? Okay, A, zero, A, A, A, A, A, A, that's kind of an interesting case. Now what cover will do, if I go into the SBY file and I also add a cover task and say in the cover task your mode is cover. So what this will actually do is it will run the cover statements and it will attempt to find a sequence of inputs that will result in this output. So in other words, what we're asking is what values of A and B result in an output of A, A, A, A. So if we run it, okay, so the first part was the BMC, bounded model checking. The second part was the cover and cover gives us a different trace. So it's basically saying for your cover statement, here is the trace that reaches that state. So let's go ahead and open up the trace. So I'm going to go to cover and I think it's trace zero. So it says what the file is here and let's see what it found, A, B, and Y. Who knows what it's gonna find it. It's essentially random, but okay, well, zero minus this value, five, five, five, six turns out to be A, A, A, A. Great, and you could do this by hand and you can find out that, yes, it's correct. So this is kind of useful to say, I want my module to end up in this state. How do I get into this state? So that's kind of nice. Now let's go into something a little more complicated, clocked logic and this is where induction comes in. Let's talk about the prove mode. Now let's talk about prove mode, also known as induction mode. So the idea here, just as in your basic high school class where you might have learned about mathematical induction, what you do is you prove a base case. Let's just call it base case. What you do is you prove that if your base case is allowed and some other case leads to a valid state transition, regardless of what this case is, as long as this is a valid state, then prove that this state is also valid. So if your base case is valid and some starting state is valid and regardless of what that starting state is, the next state is determined to also be valid, then by definition, all of your states are going to be valid. Okay, so I'm gonna start with my skeleton and I'm going to define two signals. One is just an input clock and the other is simply an output. Now internal to this module, I'm going to set up an internal register called, I don't know, R and then I'm gonna say that always on the positive edge of clock, I want R to be equal to R plus one. Okay, that's great. So on every clock, R is gonna increment, but what about Y? Well, I'm gonna say that I'm gonna assign Y equals R so that Y always tells me what the state of R is. Notice that this is not clocked, so it's just Y is whatever R is at that time. Okay, now let me go into the formal method and again, we define the API and we define our signals to be connected to our module. Let's get rid of the assertions. Okay, so here's the thing. How exactly do we test a counter? Well, earlier I said that in order to test a counter, really what you wanna test is that given some state N, you reach state N plus one. So you would think that you would want to assert that Y is equal to, well, what is it equal to? Well, it's the past Y plus one, but what exactly is past Y? Well, you can define logic in your formal module. So for example, I might say, well, we're gonna keep past Y around. So we're gonna call this past Y and always at positive edge of clock, we want past Y to store Y. So now whenever we access past Y, that's going to be Y as it was one clock earlier. The other thing that I wanna do is only run these assertions on the positive edge of the clock. Now you could do this on the negative edge of the clock, but then there is a special option that you need to specify, which is multi-clock on because then I think the engine needs to know that this clock is not actually synchronized with the prover clock or something like that. Again, I'm no real expert in formal verification. I just know enough to get this done. So anyway, so this is the thing. Now, if I try to prove this, it's gonna fail pretty much immediately. And for the most part, the reason is that I haven't initialized my register and of course I haven't initialized past Y. So these could start at any random point. So immediately, because Y is gonna be random and past Y is gonna be random, the prover or the bounded model checker is just going to pick random values that violate this assertion and say, oops, the assertion is violated, sorry, doesn't work. So let's take a look and try it. This pass is for the cover stage. Let's remove that task completely because we're not doing any cover statements. We're just doing bounded model checking. So there we go, we failed and it will output a trace showing us why it failed. Now, if we take a look at why it failed, we can see, all right, so there's our clock toggling along, there's past Y, zero and there's Y, zero and one. You might ask yourself, well, it looks like past Y is zero but in fact there is a positive transition right over here and what's the past of Y here? Well, it's basically random. It's sort of like off the beginning of time. It's prior to the beginning of time. So this is a typical problem that you're gonna run into when you're trying to validate clock logic and there's a formula that you use, a kind of a recipe. You create a single bit called past valid and you initialize it to one. So you initialize it to zero and then you say, always at the positive edge of the clock past valid gets one. So what this means is that past valid is gonna start out at zero, then you're gonna get one clock and then past valid is gonna be one. At that point you can check past values. So we're gonna fix this up by saying if past valid, then assert that past Y is Y. Now this may still fail again because random values but let's just see what happens. Oh, and actually it passed. So this is nice. And again, the reason that it passed is because we skipped effectively one cycle. So we sort of allowed the counter to increment from whatever random value it started with. Now it's possible that there may be a bug in your counter implementation where you start from a random value and it will increment and then fail at some point. So for example, let's just introduce a bug. So if R is equal to 64 bit value hex AAAA, zero, zero, zero, zero, then R gets zero, else R increments. So this will work as long as you don't count up so high that it reaches this value and then it'll go back down to zero. So this is a bug that we've introduced and let's see what happens when we run this. Okay, actually it shows that bounded model checking failed and it's giving us a trace. So we can find out Y and in fact you could see that it says, well when Y is AAAA, zero, zero, zero, zero in the past then the next clock is zero, which of course is incorrect. So it has actually figured out which inputs cause this assertion to fail. So let's go on to K induction or the prove mode. So I'm gonna change this task to prove mode. Let's just get rid of the bug. So what prove mode does is it basically says, okay, here's 10 steps. So I'm going to assume that this is correct. I think the way it works is it converts assertions to assumptions. So there's another keyword that you can use called assume. And in fact, what we can do is we can assume past valid equals zero initially. And what that will actually do is force the engine to assume that past valid is zero initially. So it's just another way of saying the same thing, but it sort of restricts the inputs. And by input, I also mean the internal state of the simple formal module. So what prove mode does is it turns all of the assertions into assumptions. So it says, okay, I'm gonna fix the inputs so that this is true. I'm going to assume that and I'm gonna force that to be true. Then on the last step, I'm gonna turn all the assertions on. And I'm gonna make sure that if in the past 10 states, the inputs are correct, then is it true that the next state is gonna be correct? So if we run this, well, we can see that it passed and you can see that there's some additional logging about induction. If we introduce a bug, so if r is our old friend, a, a, a, a, a, 0, 0, 0, r gets 0 else r gets r plus one. So if we introduce that bug, huh, we can see that it failed. Unfortunately, it failed with a crash. I assume that that's a bug that's gonna be fixed. But the point is that there is actually a trace. Engine zero trace induct dot VCD. So let's take a look at it. So it's simple formal underscore the mode, which is prove engine zero trace induct dot VCD. Okay, and let's see what it found. All right, so you can see that there's a whole bunch of clocks. And again, this is a function of the number of time steps that we allow it. So it says, assume that the assertions are true for how many steps? One, two, three, four, five, six, seven, eight, nine, 10 steps and then take a look at the last one. So I'm just gonna expand this a little bit. So we can see that y is, let's see, a, a, a, nine, f, f, f, b. So you can guess what's gonna happen. It continues. And then of course you can see that it goes to a, a, a, zero, zero, zero, zero, and then down to zero. And that is where the induction fails. So one thing is that it does have this sort of extra cycle at the end. You can kind of ignore that. This is where the induction actually fails right here. So basically what it's done is it's assumed that your assertion is correct and indeed the assertions are correct until the assertion is no longer correct. So it's a more rigorous way of proving that a more complicated state machine will actually work. So let's play around with this a little more. Let's suppose that this is actually a register that you can write to, for example. So I'm gonna say input logic 63 down to zero. We'll just call it in. The output is still gonna be the value of the register, but here what we're gonna do is we're gonna say in, no, r gets in. That's it. Now this is fairly straightforward. There's not really a whole lot that you can do for formal verification, but let's go ahead and copy the API over, copy the signals over. We'll keep our past y. Do we need that? Maybe? Maybe we need that? Okay, we need our past valid because we're gonna check from one state to the next. And if the past is valid, then y should be equal to past y. So regardless of what you write, that should be output. So this is a fairly simple check. Let's just make sure that this is actually working. We're running under prove mode. Oh, we failed. That's interesting. Why did we fail? So let's take a look. So here's clock, here's in, here's past valid. There's past y and there's y. So why did we fail? Zoom in. Actually, let's do something simpler, which is go to BMC mode, right? And we can limit the depth to something like five because really all we need are two clock cycles. So let's run this and we failed. Let's find out why. So this is running under BMC mode and it's engine zero and the trace file is trace.vcd. Okay, let's take a look. There we go. The trace is a lot shorter now. It's only three clock cycles. So we can see that past y is zero. No, here we go. Past in is eight followed by zeros. The reason is that we actually want to test against past in, not past y. Now, again, we can go through the trouble of defining this past variable for in, but instead we're going to use a little syntactic sugar. It's called dollar past of in, okay? And one of the keys is that whenever you see dollar past, you should make sure that your past is valid because otherwise the engine, it's basically undefined behavior, what the past prior to the beginning of time actually is. Okay, so let's go ahead and run the test again and it passed. So that's fairly straightforward. You know, whatever we wrote is whatever we are looking at now. Let's suppose we add a write enable. So input logic write enable. So here, always on the positive edge of the clock, only if write enable is true, then we load the register, otherwise we just leave it alone. So we're going to have to copy the API, copy the signal over. Now, if we run this test, it's obviously going to fail because most likely the solver is going to not activate write enable. So let's take a look if that's true and done is fail. And if we look at the trace to find out why it failed, well, we can see that write enable remained at zero all the time and the initial value of the register is probably just random. So where's the register right here? Okay, so the initial value of the register is zero and you can see that the past input was eight. The current input is zero. So that certainly didn't work because we have some random input. We didn't actually write it and then we're testing that why is the same as what we attempted to write when we actually didn't write it. So how do we fix this? What we want to do is say, because an assertion, the assertion should hold for every clock cycle in this case, but here we want to say that if the past of write enable was true, then we want to make this assertion. Now let's see what happens. Okay, we've passed. This is good. And in fact, what we can do, let's use a cover statement to figure out how exactly do we write into this register? Now, this is a simple register. We know how to write into it. Basically you set write enable, you set your input, you clock it and then you can read the output immediately. But let's suppose you didn't know that, right? So let's add an additional statement. Cover that, let's say y is equal to, well, let's pick our magical value. How do we end up in this state? Okay, now there are two ways that we can end up in this state. The first way is that we actually write this value. But the second way is if you look at our register, you can see that it's not initialized to any particular value. And we don't have actually a reset signal here. So the engine is free to choose any initialization value of R it wants. And what it's gonna do is it's gonna pick this as an initialization value. So instead, what I'm just gonna do is I'm going to initialize R to zero. Take away BMC mode and well, actually let's keep BMC mode so that we can keep our assertions and we'll add cover mode, mode cover. Now again, I'm pretty sure that with a depth of five clocks we can actually reach that cover statement. So let's see what happens. Okay, so here we see that we've passed BMC and we've also passed cover. So cover is gonna give us a trace. How do we get into that state? Let's take a look. Okay. And honestly, this is almost magical to me that it's actually able to find a state where the cover statement is true. So you can see that it decided that you must set the input to AAAA, zero, zero, zero, zero. You must set write enable high and then on the next clock, write enable goes low and the output goes to AAAA, zero, zero, zero. It's almost magical, but it's actually mathematical. It's mathematical. So anyway, that's a really, really simple introduction to formal verification. So we've covered bounded model checking, we've covered prove mode or induction mode, not in any great detail there are differences. Now what I suggest you do is you go to zipcpu.com. So this is a blog that has a lot about formal verification. So for example, if you, okay, so if you just do a search for zipcpu and formal, you'll see my first experience with formal methods. So it basically talks about the bounded model check, the induction, formal declarations, it talks about assume, it talks about, it talks about, okay, there's rows and stable. So these are actually some nice keywords that are built in. Briefly, stable for something means that the past of X is equal to X. In other words, it hasn't changed. In other words, it's stable. Rows. And I think this is only valid for a single bit values. So rows implies that X was zero and now it's one. So in other words, the past of X was zero and currently X is one. And of course, fell is the opposite, where the past of X is true and X is no longer true. Now, one of the other interesting things is that not fell, what does that imply? Well, it doesn't imply rows, it could also imply stable as well. So be careful with negating fell and rows. Finally, past also has an additional syntax where past of X comma one means the past of X. Past of X comma two means X as it was two clock cycles ago. So it would be equivalent to the past of the past of X and so on, you could keep increasing that. One pitfall with this is that you have to make sure that the past is valid. So if you're using some value two cycles ago, then you need to make sure that the beginning of time was at least two cycles ago. And how would you do that? Well, you have a past valid, you could also include a past valid too. For example, like this, logic past valid two. Initially, we're going to assume that past valid is two and then always at the positive edge of the clock. If the past is valid, then past valid two gets one. And then you wanna gate all of your assertions and covers on past valid two. So now you could be assured that there were two clock cycles in the past and now dollar past in comma two is always valid. It doesn't go past the beginning of time. So that's what that's all about. So there are a lot of examples on this website and they are in general a little bit more complicated than the very simple examples that I've shown. That's because I only wanted to give you an extremely gentle introduction to formal verification just to get you started, just to install the packages and just to get some really dirt simple examples running. That's really all I wanted to cover. So again, here is that. So again, here is that one pager where you can get YOSIS and Symbiosis and YISIS. Those are all required. GTK Wave, which I highly recommend because if you've got traces, you're gonna have to display them somehow and that's what GTK Wave is for. And then if you're using Visual Studio Code, which I also recommend, you should install the Verilog HDL system Verilog extension and also install Icarus Verilog. And then I should probably say that for, let's see, file, what is it? Preferences, settings. If you just search for Verilog, you can see that the winter is set to iVerilog. There are these other things, but I think there's Verilator, there's ModelSim, so. And I don't think Verilator actually runs under Windows or if it does, it's really difficult to get running. So just stick with iVerilog. Okay, I think that's about it. So I hope this was helpful. Again, I didn't cover everything and I'm not an expert in formal verification. I'm just an amateur, I just got started with this. So thanks for watching. See ya.