Saturday 21 September 2019

Fixing a Blank Screen

Foreword

In the previous post we have implemented joystick control to our C64 FPGA module that enabled us to start the game from the Intro screen.

The game, however, started frozen and the screen looked garbled.

To fix this  frozen garbled screen,  my next goal was to implement Raster interrupts, as well as multicolor text mode.

While trying to implement Raster Interrupts and Multicolor Text mode, I was presented with a nasty suprise when the Zybo board started up with our C64 design: A Blank screen!

This Blank Screen proved indeed to be a challenge and a half to debug.

The Take Home from this exercise proved to be quite an important one for general FPGA design, so decided to dedicated this post on how this issue was resolved.

Background

You might recall once (quite a number of posts back), that our 6502 core just started crashing without any apparent reason.

After some careful investigation, I found that this crash was caused by a newly implemented clock divider for generating a 1MHz signal from a 8MHz signal by means of a binary counter.

The quirks of this matter was that as soon as you use a binary counter for a clock source, you should ensure you implement the correct constraints in your design. If you don't, you might end up with a noisy, spike clock signal causing all kinds of unexpected behaviour.

Since I fix the issue with the necessary constraints, I never really had the same issue again while I was adding more functionality to the design. That is, up until now.

I was making good progress with Raster interrupts until I was greeted by a blank screen while testing some of my changes on the Zybo board.

From similar issues in the past with our C64 FPGA design, a blank screen tells me that our 6502 module crashed earlier in the process, which in this case was caused again by a clocking constraint violation.

The Solution


I am going to try and explain how I managed to isolate this issue. Before we do, let us just recap on how I solved the clock divider issue originally, the surrounding technical details can make a bit more sense.

Here is the snippet of code for generating a 1MHz clock and a 2MHz clock in our design:

...
reg [2:0] clk_div_counter = 0;
...

    always @(posedge clk)
      clk_div_counter <= clk_div_counter + 1; 

    always @(negedge clk)
      clk_1_enable <= (clk_div_counter == 7);
...
    always @(negedge clk)
      clk_2_enable <= (clk_div_counter == 2) | (clk_div_counter == 6) ;

       BUFGCE BUFGCE_1_mhz (
       .O(clk_1_mhz),   // 1-bit output: Clock output
       .CE(clk_1_enable), // 1-bit input: Clock enable input for I0
       .I(clk)    // 1-bit input: Primary clock
    );

       BUFGCE BUFGCE_2_mhz (
       .O(clk_2_mhz),   // 1-bit output: Clock output
       .CE(clk_2_enable), // 1-bit input: Clock enable input for I0
       .I(clk)    // 1-bit input: Primary clock
    );
...

From the clk_div_counter, we are creating clock enable signals that drives a BUFGCE block, which is essentially a buffer.

A key feature of these two buffers is that is physically close to clocking circuits on the FPGA die. Also, the clock output pulse from these buffers can drive quite number flip flops, while maintaining a clean waveform.

Apart from this code, we still need to define some constraints:

create_generated_clock -name clkdiv1 -source [get_pins design_1_i/block_test_0/inst/BUFGCE_1_mhz/I0] 
                       -edges {1 2 17} [get_pins design_1_i/block_test_0/inst/BUFGCE_1_mhz/O]
create_generated_clock -name clkdiv2 -source [get_pins design_1_i/block_test_0/inst/BUFGCE_2_mhz/I0] 
                       -edges {7 8 15} [get_pins design_1_i/block_test_0/inst/BUFGCE_2_mhz/O]



So, where did things went wrong in our existing design? It all becomes clear when you have a look at the schematic view of the Synthesised design within Vivado. Have a look at the following extract of the schematic:

This corresponds more or less to the Verilog code shown earlier. Needless to say, both the registers clk_1_enable and clk_2_enable ended up as a flip-flops, which can be quickly identified by the triangle next to the C input.

Both the Data inputs is fed from a bit from the clk_div_counter. This is where things starts to get interesting. If we follow the schematic, we see that there is not really a direct path from clk_div_counter to the enable registers.

Instead, clk_div_counter enters the VIC-II module first, and goes past quite a number of Flip-flop and LUT elements. This signal then eventually exits the VIC-II module and it is only at this point where this signal enters the enable registers.

In short, it quite a long path between clk_div_counter and the enable registers. This is not the ideal, considering that we want to generate clock signals.

The question is: How do we shorten the length between clk_div_counter and the enable registers? The short answer is that we should create a duplicate register of clk_div_counter that is serving just the enable registers.

During optimisation, however, Vivado might remove our duplicate register and we will be back at square one. So, we need a way to tell Vivado to keep our duplicate register.

There is indeed an attribute we can use to keep our duplicate register declaration called equivalent_register_removal. We would use this attribute as follows:

...
(* equivalent_register_removal = "no" *)
    reg [2:0] clk_div_counter = 0;
(* equivalent_register_removal = "no" *)
    reg [2:0] clk_div_counter_cycle = 0;
...
    always @(posedge clk)
      clk_div_counter <= clk_div_counter + 1; 

    always @(posedge clk)
      clk_div_counter_cycle <= clk_div_counter_cycle + 1; 
...

So, the one register we would use for our clock divider and the other one for our VIC-II module.

This would solve our blank screen problem.

In Summary

Originally I intended for this post to implement Raster Interrupts as well as Multicolor text mode.

During the implementation of this functionality, I was faced with a blank screen when the Zybo started up with our C64 design.

The troubleshooting this Blank Screen proved to be quite a challenge, and I rather decided to dedicate this post on what the underlying problem was.

All in all this Blank screen was caused by timing constraints for our clock divider.

In the next post we will eventually get to the implementation of Raster interrupts and Multicolor text mode.

Till next time!

2 comments:

  1. Excellent series Johan!
    They provide a very good insight in how to simulate the good old 6502 in an FPGA.
    Learned a lot from it.
    Hartelijk dank hiervoor!
    Ton

    ReplyDelete
    Replies
    1. Thanks Ton, its a pleasure! Met veel plezier :-)

      Always nice to hear that someone learned something from this series.

      Hope the series also motivated you to get started with FPGA programming and playing with emulators :-)

      Delete