In Part I, I went through the fairly painless process of installing and setting up my first FPGA development board. In this post, I'll be describing my journey through Verilog and paradigm shifts needed to go from software development to silicon development.
My first FPGA
The Terasic DE0-Nano development board has 8 green LEDs on it, and the tutorial project that comes in the user manual is a simple counter that speeds up when you press one of the buttons on the board, and slows down again when it is released. But I'm not going to describe the tutorial, instead I wanted to get stuck in to something beyond blinkenlights.
RS232 serial interface
The RS232 serial specification is venerable - it was introduced in 1962 according to Wikipedia, and covers a lot more than just the actual serial data protocol, there are defined voltages you must use, other communication pins for deciding which device is talking and all manner of fun. But these days of Arduinos and FTDI chips, most of that has been discarded, and the part of the spec that deals with the bits of data coming down the wire is pretty much all that's left. But for my purposes that's absolutely fine, I want a device that takes a serial stream from a microcontroller, does something with the data, and spits something out again. The first problem however is getting the data into the FPGA...
As a software engineer, I have wrestled with multithreading and all the joys of race conditions that are almost inevitable. Imagine this but on a massive scale, that's what developing for an FPGA seems like. At the highest level, you have "modules", these have a set of inputs, usually at least a clock and a reset, and at least one output. So far so good. Within each module are essentially two types of processing, combinational logic (note this is not combinatorial as you will see written quite often) and sequential logic. Both of these refer to assigning some value, but they happen at different stages of execution. In Verilog, sequential logic happens within an always block:
always @ (clk posedge) begin a <= b + 1; c <= a; end
This says, on the rising edge of the clock pulse, assign the value of b + 1 to a, and a to c. But despite it being called sequential, this doesn't happen sequentially as I would know it, but concurrently, and at some point after the block has executed. So c will be set to the original value of a and not to b + 1. Of course, on the next clock cycle a will have been updated to b + 1, and so c is always 1 cycle behind a. Note that this kind of nonblocking assignment uses the <= operator.
Combinational logic happens at a different part of the execution pipeline, and is handled immediately. Often this is written outside of an always block (apart from when it's another type of always block... yeah, told you this was odd). So a simple case might be:
assign led = x & y;
This assigns the result of a bitwise and of x and y to led. As soon as either x or y's state changes, led will be updated. Note here the = operator is used to denote blocking assignment. The assign keyword is there to really drive home what you want to do because, you know, Verilog.
A second kind of combinational logic can occur in the different kind of always block:
always @ (x, y) begin foo = x & y; end
The first thing to note is that there is no posedge (or its counterpart negedge), this block effectively gets executed whenever x or y changes. Inside the block the = operator is used, but the assign keyword is absent.
Verilog allows you to mix and match combinational logic and sequential logic in the same always block, but it seems the general consensus is avoid it at all costs! Because although Verilog allows you to do it, the tool that generates the actual FPGA configuration, the synthesiser, will have a harder time figuring out what you meant and may get it wrong. This is because both Verilog and its nemesis VHDL were designed for simulating hardware, and the naturally sequential nature of CPUs means your results may differ.
I will get onto the actual design of my RS232 receiver, and how the hardware equivalent of printf() debugging is to use an oscilloscope.