Everyone working on a video application using an FPGA seems to start with Pong, so why should I be any different? I put together this Pong demo as an exercise to help get more familiar with Verilog, and gain some experience working with the Xilinx tools and the Spartan 3A FPGA starter kit. It was very slow going at first, but things are slowly beginning to make sense to me now. And hey, I’ve got Pong!!pong.v – Verilog HDL source pong.ucf – user constaints file, with pin-mappings for the Spartan 3A starter kit
Some of the ideas were taken from fpga4fun’s Pong tutorial, and the quadrature decoding logic for the rotary knob was ripped from the tutorial verbatim.
I found that the most difficult part to get working was the collision detection. My brain kept getting tripped up by the difference between writing software where statements are executed sequentially, and HDL statements defining a bunch of operations that all happen at once. I ended up with something like this:
always @(posedge clk) begin if (collided) direction <= !direction; if (direction) position <= position + 1; else position <= position - 1; end
That looks fine for sequential code, but in hardware it didn’t work. When the ball reached a point where a collision was detected, the hardware would switch the direction and increment the position simultaneously. The position increment used the old direction value, since it was happening in parallel. The result was that the ball would move one step deeper into collision territory. On the next clock, it would move in the new direction, but since it had been two steps into collision territory, one step wasn’t enough to get out, so another collision was detected and it reversed direction yet again. This caused the ball to get stuck in the wall. Confused? Me too.
I ended up solving this by introducing an XOR:
always @(posedge clk) begin if (collided) direction <= !direction; if (direction ^ collided) position <= position + 1; else position <= position - 1; end
I think there must be some more elegant solution, but I didn’t find it.
I also had trouble handling the initial conditions, setting the ball position and direction to appropriate values at startup. Normally I’d do this with a reset line, but I’m not sure how to do it in an FPGA. The Spartan 3A starter kit doesn’t provide any kind of reset input that I could find in the documentation. And at any rate, it would need to reset the logic of the design, without resetting the FPGA itself, which clears the design entirely until it’s programmed again.
I later discovered from James Newman that “initial” blocks are actually synthesizable, so you can do something like:
initial begin position <= 200; end
That blew my mind, because I must have read 100 different Verilog guides that say initial blocks can’t be synthesized into hardware. And yet, it’s true. I’m very curious how this actually works.
I want to review the Pong design, because it feels way more complicated than it needs to be. I described this to James as Verilog making me lazy. It seems way too easy to write some complicated expression involving a dozen equality comparisons, greater/less than comparisons, and ANDs and ORs, when with a little more thought, the logic could probably be substantially simplified. For instance, I think
wire ball = (xpos >= ballX && xpos <= ballX+7);
could be rewritten as
wire [9:0] delta = xpos - ballX; wire ball = (delta[9:3] == 0); // or even wire ball = ~(|delta[9:3])
which only requires a 10-bit subtractor and a 7-input NOR, instead of two 10-bit comparators and a 2-input AND. Or maybe:
reg[2:0] ballCount; always @(posedge clk) begin if (xpos == ballX) ballCount <= 7; else if (ballCount != 0) ballCount <= ballCount - 1; end wire ball; assign ball = ballCount != 0;
That’s a 3-bit down counter, and a bunch of XORs and NORs. I’m not sure if that’s better, but you get the idea. Once you start writing complex clauses of nested ifs and lengthy boolean expressions, it’s easy to lose sight of the underlying hardware implementation.11 comments