It's been a long time since my last post. School is always rough, never enough free time, and even when you do have said free time, you don't always want to spend it being productive. But that outta the way, here's the conclusion to my Conway's Game of Life series of posts.
It's time to talk about troubleshooting! This is a continuation of my Conway's Game of Life series, continued from the Architectural promised land.
The Need to Troubleshoot
One of the most important parts of design and implementation. Things, especially complex things, rarely go according to plan on our first attempts and we have to be capable of figuring out what went wrong and how to fix it.
Another thing I know many people face is the willingness to go to the debugger in programming, or in this case the simulator. I've observed in myself and my class mates a very strong aversion to using the simulator to figure out what is wrong with our SystemVerilog or Verilog designs. Rather, we prefer to waste hours on end trying to fix the problem by just looking at the code and figuring out what is wrong.
This will invariably take longer than it needs to, and you will almost inevitably end up using the simulator and figuring out the problem in much less time anyways, so you may as well use it right off the bat.
Because the whole design just runs without any user interaction, simulating it requires only the simplest testbench, with just a clock being input into the test module. The first, and main, roadblock I ran into was the size of my block rams.
What I wanted to do was follow each pixels data through the whole path and look at the values being read and written and from which locations in memory they were being read from and written to. This would allow me to find all of the potential off by one errors in the design.
The block rams were too large though, and the simulator built into the Vivado Design Suite would not open the block ram for me to look at it. This would actually turn out to be a huge blessing in disguise though since it would greatly simplify the troubleshooting process.
Scaling the Problem
What could I do without having to change large portions of the design I already had? I could make the block ram only be a 4x4 square of cells instead of a 640x480 grid of squares. I wouldn't have to change the VGA driver to get this to work at all, since I could just send the 2 MSBs of each address request to the video ram. So I ran with that idea, scaled down the size of the block ram and started trouble shooting the transfer logic.
I don't remember what the actual specific problem was for the transfer logic, but it was definitely an off by one error. Sometimes with designs like these, specially when you're new to this sort of stuff, it can be hard to remember what values are being presented where, and on which clock cycles. This is a problem depending on where you are incrementing values. Using the small 4x4 grid allowed be to easily see where the problem was and correct it, making the transfer logic work perfectly in a short period of time.
The actual game logic was not nearly so simple...
Fixing the Game Logic
Looking at fixing the game logic, even with the board shrunk down to 4x4, was daunting. So the first thing I did was draw out the entire board, with its initial state. I then calculated by hand the next state of each cell, along with the coordinates and memory address of each cell for easy reference when I was looking at the outcome in the simulator.
In the center of each cell is the initial state, with the surrounding cells values being written around it to ease calculation of the next state, in the bottom left is the ram location, and the top right is the next state of that cell. This diagram was invaluable in checking the effectiveness of my design.
Going through every clock cycle to check every read and write being done by the game logic module was extremely tedious, but in the end it was the easiest and fastest way to find the errors in my logic. It was like the transfer logic, in that I was not taking into account different values to presented along with counters being incremented too soon. You can see the value at the top of each cell, is the time in the simulator where the calculation of that cell started, to ease finding it again after changes were made.
When all of the logic, for every location on the 4x4 grid, was checked and known to be working, I implemented the design to run on the Basys 3 and see what it looked like on the tv.
It's like some sort of seizure machine, which scared me at first, but I realized that the speed at which the board was changing and the size of the pixels were causing this, and that most likely it would look fine at the real resolution I wished to run the game at.
Completion of the Project
I know this post was a long time coming. I will strive in the future to write more consistently, which will allow me to go into more detail as well (who knows if anyone wants this).
In the end, this was an incredibly exciting project. I pushed myself to come up with clever ways to solve limitations I placed on myself which gave me quite a few learning opportunities. Writing all of these blog posts was also a learning opportunity. I know my writing isn't the best, but writing more will lead to improvement.
And here is the final product.
All of the files for this project are located here, and let me know if you have any suggestions for improvement, of which there are probably many, or if you have any questions about its operation.