Saturday, 5 October 2019

Implementing Sprites: Part 1

Foreword

In the previous post we have implemented Raster Interrupts as well as Multicolor Text mode.

This enabled us to completely render the status bar and the Background of the game. We were even able to move between screens of the environments.

There was, however, a crucial piece of the game play experience missing: The characters were invisible!

The reason I our characters were hidden, was because we haven't implemented Sprites in our VIC-II module yet. So, our next focus in this Blog series would be to implement Sprites in C64 FPGA implementation.

To implement sprites in an upcoming C64 emulator can be quite a daunting task. The following tasks come to mind, just to name a few:
  • Coordinate memory access between fetching Sprite data, fetching screen memory content and fetching character image data.
  • Mixing the sprite images with Text /bitmap mode graphics to get the final picture
  • Adding functionality to either show sprites in front of text or behind it.
  • Dealing with transparency
  • Implementing multicolor mode
  • Adding functionality to stretch a sprite in ether the Y- or X-direction
I have therefore decided to split the implementation of sprites into a number of separate posts.

In this post we will focus on showing a single sprite in front of text.

To test the resulting Sprite implementation, we will be using a simple Basic program for moving a sprite across the screen.

Retrieving Sprite Data from Memory

Let us start our journey of implementing sprite rendering by thinking how we will be fetching sprite data from memory.

A good start will be to review how our VIC-II currently interface with memory to get image data. Here is a quick outline:
  • Output port named addr for sending required address of which we want data for.
  • Requested data is send to input port data_in. This port is 12 bits wide, eight bits data and 4 bits from Color RAM. In this way for each screen location the character code and associated color arrives simultaneously, thus eliminating the need for an extra memory cycle to get the color code.
  • Memory requests is clocked at 2MHz. This translates to 2 memory accesses during an eight pixel period.
If you went through the particulars of the VIC-II, you will see that it clocks memory accesses at 1MHz. So, why am I clocking memory at 2MHz in my VIC-II implementation?

The key to this answer lies in the fact that within a C64 memory access happens on both the rising edge and falling edge of a 1Mhz clock cycle. The VIC-II access memory on the rising edge and the 6510 CPU on the falling edge of a 1MHz clock pulse.

There is, however, cases where the VIC-II will access memory on both the rising and falling edge. This happens at the beginning of each character line, where the VIC-II needs to retrieve the character code as well as the relevant pixel data to display. The VIC-II needs that extra time to retrieve the code for the character to be displayed, so the CPU cannot do any memory accesses during these times.

As we can see memory access times is very tight for the VIC-II, so one might wonder how the VIC-II manages to get some memory cycles for retrieving spite data. This is where Christian Bauer's write-up on the VIC-II comes to the rescue, as explained here. The section of interest is 3.6.3, Timing of a raster line.

In this section a couple of VIC-II memory access diagrams is shown for a couple of scenarios. The scenario where sprites 2-7 is active on a raster line gives us a very good idea where the VIC-II rertrieves Sprite data from memory (I have added the legend for convenience):

Cycl-# 6                   1 1 1 1 1 1 1 1 1 1 |5 5 5 5 5 5 5 6 6 6 6 6 6
       5 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 |3 4 5 6 7 8 9 0 1 2 3 4 5 1
        _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _| _ _ _ _ _ _ _ _ _ _ _ _ _ _
    ΓΈ0 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |_ _ _ _ _ _ _ _ _ _ _ _ _ _
       __                                      |
   IRQ   ______________________________________|____________________________
                             __________________|________
    BA ______________________                  |        ____________________
                              _ _ _ _ _ _ _ _ _| _ _ _ _ _ _ _
   AEC _______________________ _ _ _ _ _ _ _ _ |_ _ _ _ _ _ _ ______________
                                               |
   VIC ss3sss4sss5sss6sss7sssr r r r r g g g g |g g g i i i i 0sss1sss2sss3s
  6510                        x x x x x x x x x| x x x x X X X
                                               |
Graph.                      |===========0102030|7383940============
                                               |
X coo. \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|\\\\\\\\\\\\\\\\\\\\\\\\\\\\
       1111111111111111111111111110000000000000|1111111111111111111111111111
       999aaaabbbbccccddddeeeeffff0000111122223|344445555666677778888889999a
       48c048c048c048c048c048c048c048c048c048c0|c048c048c048c048c04cccc04c80

  c  Access to video matrix and Color RAM (c-access)
  g  Access to character generator or bitmap (g-access)
 0-7 Reading the sprite data pointer for sprite 0-7 (p-access)
  s  Reading the sprite data (s-access)
  r  DRAM refresh
  i  Idle access

  x  Read or write access of the processor
  X  Processor may do write accesses, stops on first read (BA is low and so
     is RDY)


Let us unpack the above diagram a bit. Right on top we have cycle number, which is a count that gets incremented at a frequency of 1MHz.

The diagram starts with the last cycle of the previous line, which in this case is 65, which is obviously an NTSC VIC-II variant.

The diagram then continues from cycle#1 to cycle#19. For clarity the cycles from 20-52 is omitted in the diagram, and we continue again from cycle 53 to 65 and the first pixel of the next line.

You will also notice that after each each cycle number there is a space. This is just for the potential time period where the VIC-II might make use of both the rising and the falling edge of the 1MHz clock cycle for retrieving data.

The Graph row shows us when the electron beam is busy drawing on the screen. Making that statement felt a weird for me since we don't really use CRT's anymore :-)

On the Graph row equal(=) is the period when the Border is drawn, and a digit the when we are actually drawing character data.

With the Graph row as reference, let us have a look at the VIC row to see when sprite data is read from memory.

We can actually see that just before we are finished drawing a screen line, which is obviously a border operation, we start reading sprite info. It starts with: 0sss. We read sprite data pointer 0 from the end of screen memory, proceeded by reading three bytes of sprite data.

We do the same for sprites 1 to 7 and we stop just before we start drawing the border of the next raster line.

Also, please note that on the diagram, there is no space between the sprite pointer and sprite data data symbols. So, when we get the relevant data for a sprite, we are using all available memory cycles.

In short, the sprite data is retrieved during the none visible parts of a screen line, or to use PAL and NTSC terminology, during the front- and back-porch of a scan line.

Implementing sprite data Memory accesses

The previous section gives us a good indication where we should implement the reading of sprite data during the drawing of line.

Admitted, in our VIC-II we don't start with a blank period on a rasterline, but rather start immediately to draw the border on the beginning of each raster line.

We will, however, use the blank period of the end of the rasterline to read sprite data.

Let us start to some calculations. There are 4 memory accesses per sprite (e.g. sprite pointer and three bytes). For sprites there are two memory accesses per cycle. So, a sprite needs two cycles to get all its data.

We will be using our x_pos counter to determine when to read sprite data. For this purpose, we write the following code:

...
sprite_data_region;
...
assign sprite_data_region = (x_pos > 368 && x_pos < 496);
...

Our rasterline is 504 pixels, so we start reading sprite data at the very end of the line. Obviously the sprite pixels will be shown in the next line.

It is also more convenient to work with an offset of 368, rather than an absolute x_pos value:

...
wire [9:0] sprite_data_region_offset;
...
assign sprite_data_region_offset = {1'b0, x_pos} - 368;
...

Within this sprite data region, each sprite gets its data in a time period 16 pixels. We can therefore extract the following information from sprite_data_region_offset:

  • bits 6 - 4: sprite number
  • bits 3 - 0: Time cycle within the data cycle of current sprite. This is useful for orchestrating the various reads that should happen for the sprite. 

Next, let us focus on address generation. With this we should keep in my mind that our VIC-II module reads data from a memory port that is clocked at 2MHz. In relation to a group of 8 pixels this clock pulse at pixel number 3 and pixel number 7.

Let us change our address generation functionality as follows:

...
reg [7:0] sprite_data_location; 
wire [1:0] sprite_byte_num;
...
assign sprite_byte_num = sprite_data_region_offset[3:2] + 2'b11;
...
always @(posedge clk_in)
if (sprite_data_region && sprite_data_region_offset[3:0] == 4)
  sprite_data_location <= data_in[7:0];
...
     always @*
       if (!sprite_data_region && (clk_counter == 6 | clk_counter == 7))
         addr = bit_data_pointer;       
       else if (sprite_data_region && (sprite_data_region_offset[3:0] < 3))
         addr = {mem_pointers[7:4], 7'h7f, sprite_data_region_offset[6:4]};
       else if (sprite_data_region)
         addr = {sprite_data_location, (sprite_0_offset + sprite_byte_num)}; 
       else
         addr =  {mem_pointers[7:4], screen_mem_pos};
...

So, in each sprite section, we need to ensure we assert the address for the applicable sprite pointer before the first 2MHz clock pulse trigger. When this clock pulse trigger the Block RAM will return the value of the sprite pointer in question.

We store this sprite pointer in a register called sprite_data_location at a pixel period after the Block RAM returned this pointer.

Sprite_data_location will be used to generate addresses for the actual sprite data. We need the following pieces of information in addition to generate the addresses for the sprite data:
  • sprite offset: Line number of the sprite we need data for as a linear address. This will be a factor of three. For instance, should we need the sprite data for line 2, we will specify 6 as the offset.
  • sprite_byte_num: Either return 0,1 or 2 of the requested line.
We will be using bits 3 and 2 of sprite_data_region_offset for the sprite_byte_num. One should rather remember that we only start reading sprite data from combination 01 and not from combination 00.

During combination 00 we are still reading sprite_data_location. We therefore need to subtract one from this bit combination to get the actual bit combination.

As a quick hack, I achieved this subtraction by one by just adding 2'b11 with the help of Two's complement.

With this piece of code in place we will be receiving the sprite data for all the sprites during the applicable time frames. It is up to use to actually catch this data at the right time and manipulate it up to the point that the sprite gets rendered on the screen.

For all this functionality it makes sense to encapsulate it in a sprite_generator module, which we will cover in the next section.

The Sprite Generator module

Let us start our Sprite Generator module by providing input ports indicating the current Raster Position and Sprite position:

module sprite_generator(
  input clk_in,
  input [8:0] raster_y_pos,
  input [8:0] raster_x_pos,
  input [8:0] sprite_x_pos,
  input [7:0] sprite_y_pos,
    );


First thing we need to calculate is the linear address for the sprite line we want data for:

...
  wire [8:0] next_raster;
  wire [5:0] request_line;
...
  assign next_raster = raster_y_pos + 1;
  assign request_line = next_raster - sprite_y_pos;
  assign request_line_offset = (request_line << 1) + request_line;
...


The calculation of the linear address involves multiplying the line number by three, which we achieve by left shifting the line number by left and adding the line number to the result. 

Next, we should add a 3 byte shift register to our sprite generator that can shift the data bytes in when it arrives, as well as shifting the bits out when we are in the display region of the sprite:

module sprite_generator(
...
  input store_byte,
  input [7:0] data,
...
    );
...
  wire sprite_display_region;
  reg [23:0] sprite_data;
...
  assign sprite_display_region = (raster_y_pos >= sprite_y_pos && raster_y_pos < (sprite_y_pos + 21)) &&
                                 (raster_x_pos >= sprite_x_pos && raster_x_pos < (sprite_x_pos + 24));
...
  always @(posedge clk_in)
    if (store_byte)
      sprite_data <= {sprite_data[15:0], data[7:0]};
    else if (sprite_display_region)
      sprite_data <= {sprite_data[22:0], 1'b0};
...

So, when we are within the visible region of the sprite we shift out the contents of sprite_date one pixel at a time. What we need to next, is to output a color for each bit we shift out.

For now we will only output the color white if the bit is a 1:

module sprite_generator(
...
  output [3:0] output_pixel,
...
    );

...
assign output_pixel = sprite_data[23] ? 4'b1 : 0;
...

One thing we should keep in mind, is that bits with the value zero are in fact transparent. So, we need to have an additional output port indicating whether the current pixel should be transparent or not.

If a sprite pixel is transparent, it just a condition indicating that the VIC-II shouldn't show the current sprite pixel. There are additional conditions in which our VIC-II shouldn't show the pixel of a sprite:
  • The sprite is disabled
  • We are not current within the area of the sprite on the screen
Let us wrap all these conditions together and output to a single port:

module sprite_generator(
...
  input sprite_enabled,
  output show_pixel,
...
    );
...
  assign show_pixel = sprite_enabled && sprite_data[23] && sprite_display_region;
...

Wiring everything up

With our sprite generator created, let us hook up to our VIC-II module.

We need to end up with 8 sprite generators. That is a sprite_generator for each sprite. However, for now, to keep things simple, we will just be using a single one.

To start with, we are going to implement some more of the VIC-II registers in our VIC-II module:

reg [7:0] sprite_0_xpos;
reg [7:0] sprite_0_ypos;
reg [7:0] sprite_1_xpos;
reg [7:0] sprite_1_ypos;
reg [7:0] sprite_2_xpos;
reg [7:0] sprite_2_ypos;
reg [7:0] sprite_3_xpos;
reg [7:0] sprite_3_ypos;
reg [7:0] sprite_4_xpos;
reg [7:0] sprite_4_ypos;
reg [7:0] sprite_5_xpos;
reg [7:0] sprite_5_ypos;
reg [7:0] sprite_6_xpos;
reg [7:0] sprite_6_ypos;
reg [7:0] sprite_7_xpos;
reg [7:0] sprite_7_ypos;
reg [7:0] sprite_msb_x = 0;
reg [7:0] sprite_enabled;

always @(posedge clk_1_mhz)
     case (addr_in)
       6'h00: data_out_reg <= sprite_0_xpos;
       6'h01: data_out_reg <= sprite_0_ypos;
       6'h02: data_out_reg <= sprite_1_xpos;
       6'h03: data_out_reg <= sprite_1_ypos;
       6'h04: data_out_reg <= sprite_2_xpos;
       6'h05: data_out_reg <= sprite_2_ypos;
       6'h06: data_out_reg <= sprite_3_xpos;
       6'h07: data_out_reg <= sprite_3_ypos;
       6'h08: data_out_reg <= sprite_4_xpos;
       6'h09: data_out_reg <= sprite_4_ypos;
       6'h0a: data_out_reg <= sprite_5_xpos;
       6'h0b: data_out_reg <= sprite_5_ypos;
       6'h0c: data_out_reg <= sprite_6_xpos;
       6'h0d: data_out_reg <= sprite_6_ypos;
       6'h0e: data_out_reg <= sprite_7_xpos;
       6'h0f: data_out_reg <= sprite_7_ypos;
       6'h10: data_out_reg <= sprite_msb_x;
       6'h15: data_out_reg <= sprite_enabled;
       
       6'h20: data_out_reg <= {4'b0,border_color};
       6'h21: data_out_reg <= {4'b0,background_color};
       6'h22: data_out_reg <= {4'b0,extra_background_color_1};
       6'h23: data_out_reg <= {4'b0,extra_background_color_2};
       6'h11: data_out_reg <= {y_pos_real[8],screen_control_1[6:0]};
       6'h12: data_out_reg <= {y_pos_real[7:0]};
       6'h16: data_out_reg <= screen_control_2;
       6'h18: data_out_reg <= mem_pointers;
       6'h19: data_out_reg <= {7'h0,raster_int};
       6'h1a: data_out_reg <= int_enabled;
     endcase

always @(posedge clk_1_mhz)
begin
  if (we & addr_in == 6'h00)
    sprite_0_xpos <= reg_data_in[7:0];
  else if (we & addr_in == 6'h01)
    sprite_0_ypos <= reg_data_in[7:0];
  else if (we & addr_in == 6'h02)
     sprite_1_xpos <= reg_data_in[7:0];
  else if (we & addr_in == 6'h03)
     sprite_1_ypos <= reg_data_in[7:0];
  else if (we & addr_in == 6'h04)
     sprite_2_xpos <= reg_data_in[7:0];
  else if (we & addr_in == 6'h05)
     sprite_2_ypos <= reg_data_in[7:0];
  else if (we & addr_in == 6'h06)
     sprite_3_xpos <= reg_data_in[7:0];
  else if (we & addr_in == 6'h07)
     sprite_3_ypos <= reg_data_in[7:0];
  else if (we & addr_in == 6'h08)
     sprite_4_xpos <= reg_data_in[7:0];
  else if (we & addr_in == 6'h09)
     sprite_4_ypos <= reg_data_in[7:0];
  else if (we & addr_in == 6'h0a)
     sprite_5_xpos <= reg_data_in[7:0];
  else if (we & addr_in == 6'h0b)
     sprite_5_ypos <= reg_data_in[7:0];
  else if (we & addr_in == 6'h0c)
     sprite_6_xpos <= reg_data_in[7:0];
  else if (we & addr_in == 6'h0d)
     sprite_6_ypos <= reg_data_in[7:0];
  else if (we & addr_in == 6'h0e)
     sprite_7_xpos <= reg_data_in[7:0];
  else if (we & addr_in == 6'h0f)
     sprite_7_ypos <= reg_data_in[7:0];
  else if (we & addr_in == 6'h10)
     sprite_msb_x <= reg_data_in[7:0];
  else if (we & addr_in == 6'h15)
     sprite_enabled <= reg_data_in[7:0];

...
end




We now have enough information to link up most of the inputs ports of our sprite_generator:

sprite_generator sprite_0(
  .raster_y_pos(y_pos),
  .raster_x_pos(x_pos),
  .sprite_x_pos({sprite_msb_x[0],sprite_0_xpos}),
  .sprite_y_pos(sprite_0_ypos),
  .data(data_in[7:0]),
  .sprite_enabled(sprite_enabled[0]),
    );

We still need to connect the input port store_byte. The following snippet of code will basically generate this signal for us:

...
reg store_sprite_pixel_byte;
...
always @*
  case (sprite_data_region_offset[3:0])
    7, 11, 15:  store_sprite_pixel_byte = sprite_data_region && 1;
    default:  store_sprite_pixel_byte = 0;
  endcase
...

So, for any given sprite data phase, pixel periods 7, 11 and 15 is the periods just after sprite data bytes was retrieved from block RAM. At these times we would like to persist the data byte to our sprite_generator.

However, this code will return the data bytes for all the sprites for a rasterline, so we cannot use as is for the store_byte input port. So, when we assign to the store_byte input port, we just an extra just to make sure the data byte is meant for our sprite_generator:

sprite_generator sprite_0(
...
  .store_byte(store_sprite_pixel_byte && sprite_data_region_offset[6:4] == 0),
...
    );


All our input ports are now connected.

Let us now see how we can use the output ports of our sprite_generator to render sprites with our VIC-II module:

wire show_pixel_sprite_0;
wire [3:0] out_pixel_sprite_0;


sprite_generator sprite_0(
...
  .show_pixel(show_pixel_sprite_0),
  .output_pixel(out_pixel_sprite_0),
...
    );
...
   assign color_for_bit = multicolor_data ? multi_color :    
            (pixel_shift_reg[7] == 1 ? char_buffer_out_delayed[11:8] : background_color);
   assign color_for_bit_with_sprite = show_pixel_sprite_0 ? out_pixel_sprite_0 : color_for_bit;

   assign final_color = (visible_vert & visible_horiz & screen_enabled) ? color_for_bit_with_sprite : border_color;
...

The actual mixing of the sprite images and the main graphics happens in the wire color_for_bit_with_sprite. If our sprite_generator asserts the show_pixel output port, the sprite pixel will be shown. Otherwise we show a pixel of the main graphics.

This concludes the sprite implementation for this post.

In the following sections we will test our implementation.

Creating a Testbed for simulation

As you can see from this post, there is quite a bit of code that needs to be written just for a single sprite to be displayed.

When undertaking a task like in this post, it is always handy to have a simulation Testbed at hand, like we had created a previous post where we implemented Multicolor Bitmap mode.

The RAM image of such a Testbed contains data for a test image that enables us to test snippets of new code within seconds.

We start by getting hold of any simple program in C64 BASIC that will display a sprite for us. For this purpose I will be using a program in the C64 Users Guide, which is discussed on pages 68 - 71.

This program will show the following image as a sprite:

The program listing is as follows:


In this program we need to make two small modifications, which involves removing the clear screen statement, and using Sprite zero instead of sprite 2.

We remove the Clear Screen statement because we want actually to see that the sprite renders correctly against a background.

We will use Vice C64 emulator to run this code and to create a RAM image for our testbed. For this I will be using the same process as we used in the previous post where we have developed multicolor bitmap mode. So I will not cover the process here.

Here is quick screenshot of Vice C64 emulator executing the test BASIC program:


We have the balloon with the BASIC program as a background. We need to create a RAM image from a Vice Snapshot for our testbed.

Our testbed should then render more or less the same image.

Test Results

Here is an image from our Tesbed


The colora are a bit different from our standard C64 startup colors. This is because a Reused the testbed from a previous post where we developed the multicolor bitmap mode.

Other than that, this image looks more or less the same as the Vice screenshot in the previous section.

However, you will realise the sprite has a bit of a offset compared to the VICE emulator screenshot. Taking the fourth data element on line 220 (e.g. value 3) as reference, you will see on the VICE screenshot the balloon is situated Southeast of the '3', whereas in our testbed image the balloon appears west of the '3'.

These kind of offsets is probably expected, since our C64 FPGA implementation is by no means 100% cycle accurate compared to a real C64.

For now we will just do some offset hacks to get the sprite displayed in the correct position:

sprite_generator sprite_0(
...
  .raster_y_pos(y_pos - 5),
  .raster_x_pos(x_pos - 16),
...
    );


With these changes, let us see how this program runs on the physical FPGA:


This correlates more or less to the VICE rendering of this BASIC program.

In Summary

In this post we have started to implement sprite functionality within our C64 FPGA.

In the next couple of posts we will continue to implement sprite functionality.

Till next time!

Tuesday, 24 September 2019

Raster Interrupts and Multicolor Textmode

Foreword

In the previous post we fixed the blank screen by applying the necessary clock constraints.

In this post we will focus on implementing Raster Interrupts and Multicolor Textmode.

Implementing Raster Interrupts

To implement Raster Interrupts, let us start by looking at all the registers that is involved with Raster Interrupts:


  • Bit 7 of D011: This is bit 8 of the Raster counter
  • D012: This is bit 7 - 0 of the Raster counter
  • Bit 0 of D019: Interrupt status bit of the Raster Interrupt
  • Bit 0 of D01A: If interrupts for Raster is enabled
When we read the bits from the Raster counter, we are basically returning the values of the y_pos registers. So, we can implement the read operation as follows:

always @(posedge clk_1_mhz)
     case (addr_in)
...
       6'h11: data_out_reg <= {y_pos[8],screen_control_1[6:0]};
       6'h12: data_out_reg <= {y_pos[7:0]};
...
     endcase


When we write to the Raster counter bits, we are writing to a compare value register, that the VIC-II uses to compare the Raster count to see when it is time to trigger a Raster Interrupt.

We implement the writing to this register as follows:

...
reg [7:0] rasterline_ref = 0;
...
always @(posedge clk_1_mhz)
begin
...
  else if (we & addr_in == 6'h12)
    rasterline_ref <= reg_data_in[7:0];
...
end
...


For bit 8 of the raster compare register we just use bit 7 of screen_control_1 register, and we just combine when we want to use the value.

The first part of implementing the Raster Interrupt would be to implement the compare operation:

assign is_equal_raster = {screen_control_1[7],rasterline_ref} == y_pos_real[8:0];

Next, let us implement the Status register for the Raster interrupt:

...
reg raster_int = 0;
...
always @(posedge clk_1_mhz)
if (we & (addr_in == 6'h19))
  raster_int <= raster_int & ~reg_data_in[0];
else
  raster_int <= raster_int | (is_equal_raster);
...
always @(posedge clk_1_mhz)
     case (addr_in)
...
       6'h19: data_out_reg <= {7'h0,raster_int};
...
     endcase
...

As you can see in the code above, to clear a raster interrupt that occurred, you would write a one to bit 0 of address D019.

There is small anomaly with the code we have just written. is_equal_raster will be one for the full direction of the line. This means that as soon as we clear the interrupt in raster_int, it will just set the interrupt at the next clock cycle.

To fix this, we need to set the interrupt only on the edge transition of is_equal_raster:

always @(posedge clk_1_mhz)
is_equal_raster_delayed <= is_equal_raster;

always @(posedge clk_1_mhz)
if (we & (addr_in == 6'h19))
  raster_int <= raster_int & ~reg_data_in[0];
else
  raster_int <= raster_int | (is_equal_raster & !is_equal_raster_delayed);


Next, we should output this interrupt from our VIC-II module, gated via an interrupt enable bit:

module vic_test_3
(
...
  output irq, 
...
    );
...
reg [7:0] int_enabled = 0;
...
assign irq = raster_int & int_enabled[0];
...
always @(posedge clk_1_mhz)
     case (addr_in)
...
       6'h1a: data_out_reg <= int_enabled;
     endcase
...
always @(posedge clk_1_mhz)
begin
...
  else if (we & addr_in == 6'h1a)
      int_enabled <= reg_data_in[7:0];
end
...

This interrupt we will hook up to the interrupt port of our CPU module. Since we already have the IRQ of CIA#1 connected to this port, we combine both together with an or operation:

cpu mycpu ( clk_1_mhz, c64_reset, addr, combined_d_out, ram_in, 
                                       we_raw, (irq || irq_vic), 1'b0, 1'b1 );

Multicolor TextMode

In a previous post we have implemented Multicolor Bitmap Mode, so for a start let us summarise the different colors for a pixel pair for both Multicolor Bitmap mode as well as for Multicolor Text Mode.

For Multicolor Bitmap mode the colors are as follows:

  • 00: Color stored at location D021
  • 01: Bits 4-7 of the associated code in Screen memory
  • 10: Bits 0-3 of the associated code in Screen memory
  • 11: Associated value in Color RAM  
For Multicolor Text Mode the colors are as follows:
  • 00: Color stored in location D021
  • 01: Color stored in location D022
  • 10: Color stored in location D023
  • 11: Associated value in Color RAM 
There is something in addition that we should be aware of for bit value 11 for Multicolor Text mode. From the value from Color RAM in this scenario we can only use bit 0 - 2 for a color code, limiting us to only color codes 0 -7 in multicolor text mode.

Why can't we use bit 3 from color RAM in Multicolor Text mode? In multicolor Text mode bit 3 have a very interesting function. If we set bit 3 to a one, it means that we really want to use multicolor text mode for this character cell.

I am sure many of you have burst out laughing when reading the previous paragraph :-) Why would you need to double confirm when you want to draw a character cell in Multicolor Text mode? This is actually a nice feature if you want to draw some characters in normal text mode, giving your picture a bit more detail where required.

For now we are just going to keep this fact about bit 3 of Color RAM in the back of our heads till a bit later.

For the two Multicolor Modes we can create two combinational logic blocks for outputting the relevant color based on the pixel pair value:

always @*
  case (pixel_shift_reg[7:6])
    2'b00: multi_color_bitmap_mode = background_color;
    2'b01: multi_color_bitmap_mode = char_buffer_out_delayed[7:4];
    2'b10: multi_color_bitmap_mode = char_buffer_out_delayed[3:0];
    2'b11: multi_color_bitmap_mode = char_buffer_out_delayed[11:8];
  endcase

always @*
  case (pixel_shift_reg[7:6])
    2'b00: multi_color_text_mode = background_color;
    2'b01: multi_color_text_mode = extra_background_color_1;
    2'b10: multi_color_text_mode = extra_background_color_2;
    2'b11: multi_color_text_mode = {1'b0, char_buffer_out_delayed[10:8]};
  endcase


Please note, as explained earlier, we are outputting only bits 2 - 0 of the Color RAM for bit value 11 when in Multicolor TextMode.

We can now cmbine the two color values, depending on whether we are in text mode or bitmap mode:

assign multi_color = screen_control_1[5] ? multi_color_bitmap_mode : 
                       multi_color_text_mode;

We are just about done. However, we still need to consider the scenario of bit 3 of Color RAM when we are in Multicolor Text Mode. This is just an extension of the the check of whether we are in Multicolor mode for the current character cell:

assign multicolor_data = screen_control_2[4] && 
                !(!char_buffer_out_delayed[11] && !screen_control_1[5]);

We use this value as follows:

...
   always @(posedge clk_in)
   if (clk_counter == 7)
     pixel_shift_reg <= data_in;
   else begin
     if (multicolor_data & (clk_counter[0]))
       pixel_shift_reg <= {pixel_shift_reg[5:0],2'b0};
     else if (!multicolor_data) 
       pixel_shift_reg <= {pixel_shift_reg[6:0],1'b0};
   end
...
   assign color_for_bit = multicolor_data ? multi_color :    
            (pixel_shift_reg[7] == 1 ? char_buffer_out_delayed[11:8] : background_color);
...

This concludes the code we need to write for implementing Multicolor Text Mode

Test Results

Here is screenshot of the start of the game with all the changes applied so far as described in this post:


We are just about there. Only thing that is strange is a band of random pixels above the Dan Dare Title bar.

These random characters is caused by changing character maps a couple of Raster lines too early. So, the raster counts of our VIC-II module is a bit different than that of a real VIC-II.

It can become quite an exercise to troubleshoot the difference. For now I am only going to fiddle with the Raster count offset till the image looks ok.

The following snippet of code will subtract a given offset from the Raster count:

wire [9:0] y_pos_minus_offset;
wire [9:0] y_pos_real;

assign y_pos_minus_offset = {1'b0,y_pos} - 5;
assign y_pos_real = (y_pos_minus_offset > 400) ? (10'd312 + y_pos_minus_offset) : 
          y_pos_minus_offset;


In this snippet of code we have chosen a offset value of 5 (which was actually my final attempt giving the correct result). In this code we also cater for the scenario where we wrap around, in which case subtracting 5 will yield a two's complement negative number.

To deal with this two's complement manipulation I have also added an extra bit to both y_pos_minus_offset and y_pos_real to avoid overflow conditions.

You will then use y_pos_real in all places where you compare raster counts or where the 6502 read raster counts:

...
assign is_equal_raster = {screen_control_1[7],rasterline_ref} == y_pos_real[8:0];

always @(posedge clk_1_mhz)
     case (addr_in)
...
       6'h11: data_out_reg <= {y_pos_real[8],screen_control_1[6:0]};
       6'h12: data_out_reg <= {y_pos_real[7:0]};
...
     endcase

With this quick fix, our game screen render correctly.

The following video show a quick tour when walk through three screens and fighting with a Treen:


Our characters is still invisible because we haven't implemented sprites yet, but at least see the messages popping up when we encounter the enemy!

In Summary

In this post we have implemented Raster interrupts and Multicolor text mode.

This enabled us to render the background screen properly of the game, as well as moving around between screens.

Our characters is still invisible because we haven't implemented sprites yet.

In the next post we will implement sprites so that our characters can appear!

Till next time!

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!

Tuesday, 10 September 2019

Adding joystick control

Foreword

In the previous post we managed to display the Intro screen for our game Dan Dare within our C64 FPGA implementation.

As with many other C64 games, to actually start the game you need to press fire on a joystick. Since our emulator doesn't feature any joystick at the moment, the purpose of this post will be to add functionality to emulate a joystick.

For the joystick we will just use the Numeric Keypad on the USB keyboard attached to the Zybo Board.

We will also just be focusing on implementing joystick port #2 of the C64, since this is the port the game Dan Dare uses.

How Joystick port#2 is wired to the C64

A good start for this post would be to see how Joystick port#2 is connected on a real C64.

The following snippet of a schematic from http://www.zimmers.net:


As you can see, joystick port#2 share wires on Port A of CIA#1 with the keyboard.

This setup of shared wires between joystick and keyboard immediately reminds us of a anomaly of joystick port#1, where moving the joystick also type characters on the screen.

One might tend to wonder why Joystick port#2 doesn't have the same effect. The answer is because we read the keyboard from port B on the CIA, which is connected to the row pins of the keyboard connector.

With no key been pressed on the keyboard, all pins would just remain high on port B of CIA#1. This is obviously by passed by Joystick #1 which is also connected to port B of CIA#1, which can pull down selected lines to zero, which the C64 will read as key presses.

Pulling down selected lines via Joystick port#2, will not have the same effect. With no keys been pressed on the keyboard, these pulled down lines would simply not propagate to port B of CIA#1.

Implementing Joystick port#2 in our C64 module

In the previous section we mentioned the concept of pulling low a line on either port A or port B on CIA#1. Thus, the keyboard and Joysticks on the C64 follows the philosophy of active when low.

Another feature of port A and port B of CIA#1 is that each pin of those ports is bidirectional.

This leaves us with the question: How do you implement a bidirectional pin in an FPGA?

One might think: Sure, instead of declaring a port pin on a module as either input or output, you can just declare the bidirectional port as inout.

You can indeed create a Verilog module with inout ports. However, as soon as you may be try to connect these ports to other Verilog modules in your design, you might end up running in circles.

This is because inout pins is really only meant for pins going to the outside world, for instance if you want to implement a I2C port on your FPGA.

The FPGA synthesis tools doesn't like it at all if you try to utilise inout ports for internal use.

So in our CIA module we would need to split our bidirectional ports into two separate ports each:

module cia(
  output [7:0] port_a_out,
  input [7:0] port_a_in,
  output [7:0] port_b_out,
  input [7:0] port_b_in,
...
    );
...

Next, we need to make a small adjustment when we read from Port A or B:

...
  always @(posedge clk)
  if(!we)
  case (addr)
    0: data_out <=  ~((~slave_reg_0 & slave_reg_2) | 
                     ~port_a_in);
                   
    1: data_out <= ~((~slave_reg_1 & slave_reg_3) | 
                     ~port_b_in);
...

Let us try and understand what is going on here.

When we read from either port A or B, a low value can either be caused by the input port, or via the corresponding output port (e.g. slave_reg_0 or slave_reg_1).

Also, the corresponding output port is enabled by either slave_reg_2 or slave_reg_3.

The combined effect of a input and output port resembles that of an OR operation, with the inputs inverted. For this reason we are doing all the negations.

Next, let us hook up port A and port B of our CIA instances:

...
    cia cia_1(
          .port_a_out(keyboard_control),
          .port_a_in({3'b111, joybits}),
          .port_b_in(keyboard_result),
...
            );
...
    cia cia_2(
          .port_a_out(cia_2_port_a),
          .port_a_in(8'b11111111),
...
            );
...

First, we hook up the five bits of our joystick to port a of CIA#1.

We also connect port A of CIA#2 to eight ones. We use the lower two bits of this port for the VIC-II banking bits. It is therefore crucial that we keep the relevant input bits high, so that the contents of the VIC-II bits doesn't get lost during bitwise operations.

Serving the joystick bits from AXI slave


Currently our AXI Slave block have two slave registers indicating which keys were pressed. Each bit position in these two registers represent the actual C64 key scan code of the key pressed.

In a similar fashion we can add a third register where each bit position represent the current posistion of the joystick, as well as whether the fire button is pressed.

Currently slave register 2 (e. g. address 0x43c0_0008), only have about three bits utilised for tape operation. So, we can just use some unused bits in this register for our joystick bits. 

We will use bits 4 to 8 of this register for the joystick bits. This falls on a nybble boundary, making it convenient to see the joystick bits when you are debugging and you see the register contents in hexadecimal format.

To wire up the joystick bits from the AXI slave to our C64 module, we would follow the same approach as we previously performed to enable keyboard access for our C64 module. I will therefore not be going into detail on this.

Redirecting Numeric Pad as Joystick bits


As mentioned earlier, we will be using the numeric pad of the USB keyboard as a joystick.

You might remember from a previous post that in order to interface a USB keyboard to our C64 module, we basically catch the USB scan codes, convert it to C64 key scan codes, and setting the relevant bit (or bits if more than one pressed simultaneously) at either address 0x43c0_0000 or 0x43c0_0004.

The C64 keyboard can produce key scan codes in the range 0 to 63. We can reuse our USB scan code -> C64 scan code routine by basically using scan codes 64 upwards for our joystick bits:

u32 mapUsbToC64(int usbCode) {
 if (usbCode == 0x4) { //A
  return 0xa;
 } else if (usbCode == 0x5) { //B
  return 0x1c;
 } else if (usbCode == 0x6) { //C
  return 0x14;
 } 

...
        } else if (usbCode == 0x28) { //enter
  return 0x1;
 } else if (usbCode == 0x2c) { //space
  return 0x3c;
 } else if (usbCode == 0x36) { //comma
  return 0x2f;
 } else if (usbCode == 53) { //play key `~
  return 100;
 } else if(usbCode == 96) { //up joystick
  return 64;
 } else if(usbCode == 90) { //down joystick
  return 65;
 } else if(usbCode == 92) { //left joystick
  return 66;
 } else if(usbCode == 94) { //right joystick
  return 67;
 } else if(usbCode == 98) { //fire joystick
  return 68;
 }
}


We invoke this method as follows:

void getC64Words(u32 usbWord0, u32 usbWord1, u32 *c64Word0, u32 *c64Word1, u32 *c64Word2) {
  *c64Word0 = 0;
  *c64Word1 = 0;
  *c64Word2 = 0;

  if (usbWord0 & 2) {
   *c64Word0 = 0x8000;
  }

  usbWord0 = usbWord0 >> 16;

  for (int i = 0; i < 2; i++) {
   int current = usbWord0 & 0xff;
   if (current != 0) {
     int scanCode = mapUsbToC64(current);
     if (scanCode == 100) {
      Xil_Out32(0x43C00008, 0);
     } else if (scanCode < 32) {
     *c64Word0 = *c64Word0 | (1 << scanCode);
     } else if (scanCode < 64) {
     *c64Word1 = *c64Word1 | (1 << (scanCode - 32));
     } else {
        *c64Word2 = *c64Word2 | (1 << (scanCode - 64));
     }

   }

   usbWord0 = usbWord0 >> 8;
  }

  for (int i = 0; i < 4; i++) {
   int current = usbWord1 & 0xff;
   if (current != 0) {
     int scanCode = mapUsbToC64(current);
     if (scanCode == 100) {
      Xil_Out32(0x43C00008, 0);
     } else if (scanCode < 32) {
     *c64Word0 = *c64Word0 | (1 << scanCode);
     } else if(scanCode < 64) {
     *c64Word1 = *c64Word1 | (1 << (scanCode - 32));
     } else {
      *c64Word2 = *c64Word2 | (1 << (scanCode - 64));
     }

   }

   usbWord1 = usbWord1 >> 8;
  }

}


We have introduced a third word c64Word2. This will be the word we will use to populate the joystick bits at address 0x43c0_0008.

Next, we need to update our old state_machine() method (our mini USB stack method) as shown by the following snippet:

void state_machine() {
...
  u32 toggle = Xil_In32(qTDAddressCheck+8) & 0x80000000;
  if (!(Xil_In32(qTDAddressCheck + 8) & 0x80)) {
   u32 word0 = Xil_In32(0x305000);
   u32 word1 = Xil_In32(0x305004);
   if (word0 == 0) {
    Xil_Out32(0x43c00000, 0);
    Xil_Out32(0x43c00004, 0);
    u32 joy = Xil_In32(0x43c00008) | 0x1f0;
    Xil_Out32(0x43c00008, joy);
   } else {
    //u32 bit = mapUsbToC64((word0 >> 16) & 0xff);
    //bit = 1 << bit;
    u32 c64Word0 = 0;
    u32 c64Word1 = 0;
    u32 c64Word2 = 0;
    getC64Words(word0, word1, &c64Word0, &c64Word1, &c64Word2);
    c64Word2 = ~c64Word2 & 0x1f;
    c64Word2 = c64Word2 << 4;
    /*if (bit < 32) {
     c64Word0 = 1 << bit;
    } else {
     c64Word1 = 1 << (bit - 32);
    }*/

    Xil_Out32(0x43c00000, c64Word0);
    Xil_Out32(0x43c00004, c64Word1);
    u32 tempJoy = (Xil_In32(0x43c00008) & 0xf) | c64Word2;
    Xil_Out32(0x43c00008, tempJoy);
    //Xil_In32(0x305004);
   }
... 


}


Basically we start with word0 and word1, which show the usb scan codes of the gets that is currently been pressed.

If no key is pressed (e.g. word0 == 0), we just set bit 4 to 8 of address 0x43c0_0008 to ones.

The End Results

The following video shows what happens when we press the fire button when we are at the intro screen of the game Dan Dare:


It faintly resembles the game as I remember, though garbled and frozen!

What we are missing here is implementing Raster interrupts for everything to render correctly, which we will cover in the next post.

In Summary

In this post we managed to implement a joystick in C64 module by utilising the numpad on the USB keyboard.

With our Joystick we managed to transition from the Intro screen to the actual, although our emulator froze at the this point.

In the next post we will be implementing Raster interrupts so that the game screen can render properly.

Till next time!

Thursday, 29 August 2019

IO Area Bank Switching

Foreword

In the previous two posts we did some development that will enable us the display of the associated splash screen while our C64 FPGA implementation loads the game Dan Dare from tape.

This development included implementing Multicolor bitmap mode as well as implementing VIC-II memory banking.

You might have noticed that throughout this Blog Series I am trying to follow more or less the same approach as I did in my Blog series where I created a C64 emulator in JavaScript. In this old Blog Series, I also mentioned at one point, that in order to completely load the game and get to the intro screen, we need to implement IO area banking.

The reason for this is because, during Game loading, we also write to the RAM area below the IO peripheral area (e.g. addresses D000-E000). If you do not implement the Banking of the IO area properly, you might end up with some weird side effects that is painful to debug.

IO Banking for reading

Let us start by implementing IO Banking for reading. That is either we enable reading of IO register in the region D000-DFFF, or we enable reading from the RAM region underneath.

As with the Kernel ROM and BASIC ROM, the banking of the IO region is controlled by memory address 1.

To familiarise ourselves with when the IO region is enabled, we consult the C64 memory map at http://sta.c64.org/cbm64mem.html.

The following extract shows us wehn th IO region is enabled:

Bits #0-#2: %1xx: I/O area visible at $D000-$DFFF. (Except for the value %100, see above.) 
We can convert this required into Verilog:

assign io_enabled = reg_1_6510[2] && !(reg_1_6510[1:0] == 0);

To either read from the IO region or the RAM below it, we write the following code:

    always @*
        casex (addr_delayed)
          16'b1: combined_d_out = {reg_1_6510[7:5], tape_button, reg_1_6510[3:0]};
          16'b101x_xxxx_xxxx_xxxx : combined_d_out = basic_out;
          16'b111x_xxxx_xxxx_xxxx : if (reg_1_6510[1])
                                      combined_d_out = kernel_out;
                                    else
                                      combined_d_out = ram_out;
          16'hd020, 16'hd021, 16'hd011,
          16'hd016, 16'hd018 : combined_d_out = io_enabled ? vic_reg_data_out : ram_out;
          16'hd012: combined_d_out = io_enabled ? line_counter : ram_out;
          16'b1101_10xx_xxxx_xxxx: combined_d_out = io_enabled ? color_ram_out : ram_out;
          16'hdcxx: combined_d_out = io_enabled ? ((addr_delayed == 16'hdc00) ? 255 : cia_1_data_out) : ram_out;
          16'hddxx: combined_d_out = io_enabled ? cia_2_data_out : ram_out;
          default: combined_d_out = ram_out;
        endcase

So, if io_enabled is false we just return ram_out for the io register read request.

Writing to IO Registers

Next, let us handle writing to IO register banking:

assign color_ram_write_enable = we & io_enabled & (addr >= 16'hd800 & addr < 16'hdbe8);
    
    cia cia_1(
          .port_a_out(keyboard_control),
          .port_b_in(keyboard_result),
          .addr(addr[3:0]),
          .we(we & io_enabled & (addr[15:8] == 8'hdc)),
          .clk(clk_1_mhz),
          .chip_select(addr[15:8] == 8'hdc & io_enabled),
          .data_in(ram_in),
          .data_out(cia_1_data_out),
          .flag1(flag1 & !flag1_delayed),
          .irq(irq)
            );

    cia cia_2(
          .port_a_out(cia_2_port_a),
          .addr(addr[3:0]),
          .we(we & io_enabled & (addr[15:8] == 8'hdd)),
          .clk(clk_1_mhz),
          .chip_select(addr[15:8] == 8'hdd & io_enabled),
          .data_in(ram_in),
          .data_out(cia_2_data_out)
            );

vic_test_3 vic_inst
    (
        .clk_in(clk),
        .clk_counter(clk_div_counter),
        .clk_2_mhz(clk_2_mhz),
        .blank_signal(blank_signal),
        .frame_sync(frame_sync),
        .data_in({color_ram_out2,vic_combined_d}),
        .c64_reset(c64_reset),
        .addr(vic_addr),
        .out_rgb(out_rgb),
        .clk_1_mhz(clk_1_mhz),
        .addr_in(addr[5:0]),
        .reg_data_in(ram_in),
        .we(we & io_enabled & (addr == 16'hd020 | addr == 16'hd021 | addr == 16'hd011
             | addr == 16'hd016 | addr == 16'hd018)),
        .data_out(vic_reg_data_out)

        );


These code changes will block writes to IO registers if io_enabled is false.

You might have noticed that we unconditionally write to the RAM region underneath the IO region. This causes unpredictable behaviour when we load the game Dan Dare from the Tape image.

We disable writes to this RAM region when io_enabled == false with the following code:

...
assign do_io_write = io_enabled & ((addr >= 16'hd000) && (addr < 16'he000));
...

     always @ (posedge clk_1_mhz)
       begin
        if (we & !do_io_write) 
        begin
         ram[addr] <= ram_in;
         ram_out <= ram_in;
        end
        else 
        begin
         ram_out <= ram[addr];
        end 
       end 
...


You will notice that when io_enabled is false, that we exclude RAM writes for the full IO region, even though we currently have IO register gaps in our current C64 implementation. This have the effect that writing to these gaps will effectively be discarded.

This behaviour will luckily not cause any issues in our scenario.

The End Result

The following video shows the end result of our development


We managed to get to the Intro screen after the loading of the game.

The credits is moving very fast, but we will fix this in a future post.

In Summary

In this post we have implemented banking functionality for the IO region. This enabled our emulator to load the Game Dan Dare completely from a tape image and display the Intro screen.

In the next post we will implement Joystick functionality.

Till next time!

Friday, 23 August 2019

Implementing VIC-II bank switching

Foreword

In the previous post we have implemented Multicolor Bitmap mode within our VIC-II module.

As a test we got the VIC-II to render the Loading Splash Screen of the game Dan Dare. In this test we hardwired the upper two bits of the VIC-II address to 1's because the splash screen data was located in VIC bank 3 (e.g. RAM address 49152-65535).

Obviously if you want to simulate the whole loading sequence from the time the C64 boot up, this hardcoding will not do the trick, and we will need to implement the full VIC-II bank switching with the aid of the DD00 register.

Therefore, in this post we will spend some time to implement this VIC-II bank switching. We will then check when we load the game, that the splash screen will eventually be displayed.

Implementing VIC-II bank switching

VIC-II bank switching is driven by peripheral register DD00, by the lower two bits. This website gives us more information about these bits:

Bits #0-#1: VIC bank. Values:
  • , 0: Bank #3, $C000-$FFFF, 49152-65535.
  • %01, 1: Bank #2, $8000-$BFFF, 32768-49151.
  • %10, 2: Bank #1, $4000-$7FFF, 16384-32767.
  • %11, 3: Bank #0, $0000-$3FFF, 0-16383.
So, these bits actually makes out bits 15 and 14 of the VIC-II address, inverted though.

Address DD00 forms part of CIA#2. So, let us start by instantiating an extra CIA instance and mapping it into the address space of our 6502:

    cia cia_2(
          .port_a_out(cia_2_port_a),
          .addr(addr[3:0]),
          .we(we & (addr[15:8] == 8'hdd)),
          .clk(clk_1_mhz),
          .chip_select(addr[15:8] == 8'hdd),
          .data_in(ram_in),
          .data_out(cia_2_data_out)
            );

    always @*
        casex (addr_delayed)
          16'b1: combined_d_out = {reg_1_6510[7:5], tape_button, reg_1_6510[3:0]};
          16'b101x_xxxx_xxxx_xxxx : combined_d_out = basic_out;
          16'b111x_xxxx_xxxx_xxxx : if (reg_1_6510[1])
                                      combined_d_out = kernel_out;
                                    else
                                      combined_d_out = ram_out;
          16'hd020, 16'hd021, 16'hd011,
          16'hd016, 16'hd018 : combined_d_out = vic_reg_data_out;
          16'hd012: combined_d_out = line_counter;
          6'h26: combined_d_out = color_ram_out;
          16'hdcxx: combined_d_out = cia_1_data_out;
          16'hddxx: combined_d_out = cia_2_data_out;
          default: combined_d_out = ram_out;
        endcase


We can now append bits 1 and 0 of DD00 (e.g. cia_2_port_a) to the top of the address generated by the VIC-II. In our current design, the final VIC-II address is called portb_add, and is defined as follows:

assign portb_add = (portb_reset_counter < 3900000) ? 
    portb_reset_counter[15:0] : {2'b0,vic_addr};

As you can see, previously we just padded bits 14 and 15 of the VIC-II address with zeros.

You might remember that the portb_reset_counter maneuver was an ugly hack to get the second port of our Block RAM to work properly. More on this later.

To finish off our memory mapping for our VIC-II, we still need to properly map the character ROM.

From various documentation on the Internet, we see that the Character ROM is visible in Bank 0 and 2 within the address range $1000-$2000 and $9000-$a000 respectively.

The following code takes care of mapping the Character ROM into the address space of the VIC-II:

if ((vic_addr[13:12] == 2'b1) & cia_2_port_a[0])
  vic_combined_d = chargen_out;
else
  vic_combined_d = ram_out_2;

Battle of the Block RAMS

With all the changes done as described in the previous section, I waited with much anticipation while the Bitstream was generated.

When I tested the resulting Bitstream, I was was greeted with a blank screen. What a disappointment!

My first instinct was that something went wrong again with the AXI modules, causing that pixel information couldn't flow between SDRAM and our VIC-II module.

However, when I inspected the framebuffer area in SDRAM (via the XSCT console), I found that the whole framebuffer area was filled with zeroes. This indicates that all AXI modules and our VGA module is functioning properly.

First of all, we know that writing to SDRAM is working ok since the framebuffer area is filled zeroes. If writing to SDRAM wasn't functioning properly, we would have seen random values within the RAM area.

Secondly, we know that reading pixel data from SDRAM is also functioning correctly. A framebuffer filled with zeroes will yield black pixels, which we are getting (e.g. a blank screen).

There is however a very faint clue on what might be the issue. The frame is also colored in black instead of the usual light blue. The rendering of the border pixels should cause the least amount problems and potentially indicates that our 6502 failed to set the Border color register of our VIC-II module.

Has the 6502 perhaps crashed because the contents of the Block RAM has become corrupted? I did after all experienced previously an issue with a dual port Block RAM configuration where the VIC-II simply couldn't get the correct data omn the one port. It was for this  issue I had to do the portb_reset_counter maneuver as mentioned in the previous section.

Going though the Xilinx community forums I found a clue on this AR#: https://www.xilinx.com/support/answers/34699.html

This post start with the heading:

Block Memory Generator v3.3 - Spartan-6 Block RAM Read First mode address space overlap ("collision") issue with simultaneous Read and Write may cause memory corruption
This sounds like something that might be of interest to us. The following paragraph is of particular interest:

A read/write on one port and a write operation from the other port at the same address is not allowed. This restriction on the operation of the block RAM should not be ignored.
In our our case it can easily happen that the 6502 writes to an address while the VIC-II is reading from the same address.

To understand when this can happen, let us look a timing diagram for a typical write operation to block RAM in our C64 module:


The top signal is the 1MHz clock signal and the 2Mhz signal at the bottom. The write enable signal is in the centre.

The write signal is active during pulses 1,2 and 3. During these periods the possibility also exist that the address on both ports can be the same, which can be conditions for memory corruption as described earlier.

To avoid these address collisions, we need to do the following:

  • Ensure the Write Enable signal is low during the 2MHz clock pulses
  • During the 1Mhz clock pulses we should ensure that the addresses on both ports of the Block RAM are different.
We implement these requirements with the following code:

assign we = ((clk_div_counter != 6) && (clk_div_counter != 2)) ? we_raw : 0;

assign portb_raw = {~cia_2_port_a[1:0], vic_addr};
assign portb_add = (clk_div_counter == 7) ? {portb_raw[15:1],~addr[0]} : portb_raw;

We disable the Write Enable signal before every 2MHz clock pulse. The we_raw should be connected to our 6502 module.

The portb_raw and portb_add assignments ensure the addresses of the two ports are different during 1MHz clock pulses. We accomplish this by just substituting the least significant bit of port bit with the inverse of the LSB of port A.

The End Result

With all the code changes, our C64 module now shows the Splash screen correctly during loading.

The following video shows the loading process up to and until the Splash screen appears:


In Summary

In this Blog we created all the glue logic, so that our VIC-II could work with Bank switching.

We also managed to display the Splash screen during the loading of the Game Dan Dare.

In the next post we will check how much further the loading process can get, and fixing issues that might arise.

Till next time!


Wednesday, 7 August 2019

Implementing Multicolor Bitmap Mode

Foreword

In the previous post we added Color RAM to our C64 FPGA design.

In this post we will implement the Multicolor Bitmap Mode within our VIC-II module. This will enable us to render the Splash screen of Dan Dare correctly during the loading process.

To verify the resulting development, I will also be developing a Test Bench in this post to simulate test our VIC-II module in isolation.

Needless to say, the ultimate test would be to see if our Test Bench would be able to render the Dan Dare Splash screen to an image file.

To do this test, we would need to have the image data of the splash screen in our C64 main memory as well as in the Color RAM.

Thus, in this post I will also illustrate how to use the VICE Commodore emulator to extract the image data for the Splash Screen.

Extracting the Image data for the Splash Screen

Let us start by tackling the goal of extracting the image data of the Splash screen.

As mentioned previously we will use the Vice Commodore emulator for this purpose.

We start off by kicking off the loading of the game Dan Dare.

As soon as the Splash screen has been loaded completely, activate the builtin Monitor. We then issue a couple of memory dump commands:


The first memory location to look at is location $DD00. Bits 0 and 1 is the upper two bits of the memory address of the VIC-II, inverted. This result to 11. This results to the last 16KB bank of RAM, which is address range 49152-65535.

Next, we should find out where to look for the image data. The answer to this is memory location D018. The bits in this location is layout as follows:

Bit 7: VM13
Bit 6: VM12
Bit 5: VM11
Bit 4: VM10
Bit 3: CB13
Bit 2: CB12
Bit 1: CB11
Bit 0: -

The bit number staring with VM is the base address of the Video Memory or Screen memory.

The Bit numbers starting with CB is the base address fro the Character Image data. In high resolution modes, which is in our case the case, it is the location for the bitmap data.

From this informstion we see that screen memory starts at $C000 and the bitmap data data at $E000.

The next challenge is to extract the image data into a file that our Test Bench/VIC-II module can use.

The easiest way to this is to to save the state of our running emulator at this point as a snapshot, and to extract the relevant portions from the snapshot.

Vice stores the snapshot as a *.vsf file. When you open this file in a HEX editor, you can identify the relevant sections with header names:



In this example we can see that the 64KB section starts with the header name C64MEM. It is not complete clear where the actual memory starts. We get more info from this in the VICE documention:

Type Name Description
BYTE CPUDATA CPU port data byte
BYTE CPUDIR CPU port direction byte
BYTE EXROM state of the EXROM line (?)
BYTE GAME state of the GAME line (?)
ARRAY RAM 64k RAM dump

Keep in mind that this gets preceded by a header of 22 bytes.

The next piece of information to extract is the Color RAM. The Vice documentation and doing some seraches on the Internet doesn't yield any particular information on where the Color RAM is stored in the vsf file.

Eventually I find the answer by looking into the source code of Vice. Within the file src/vicii/vicii-snapshot.c I found the following:


So, the Color RAM in located indeed within the VIC-II module section in the vsf file.

To convert this info in a format suitable for our Test bench, we just paste the relevant HEX data into a Text Editor and replace all spaces with newlines.

The current plumbing of our VIC-II module

It has been a while since we look into the inner workings of our VIC-II module. I therefore think it would be a good idea for us to do a quick refresher on this so that we can end up with a better idea on how to implement the Multicolor Bitmap mode.

To give us a baseline as a reference, I have created the following diagram:


In this diagram we have basically zoomed into the first three character rows, and on each row I am only showing the first two characters. The area on the left in solid purple represent the border.

You can see that on each line we already start reading while still in the border area.

You can also see that we are only reading the character codes only at the first pixel row of each character row. In fact, when we get the character codes, we are storing it in a 40 byte buffer. For the remaining pixel rows we are getting the character codes from this buffer.

Let us move into a bit more detail on our VIC-II module by looking at some important signals:


If both the visible_vert and visible_horiz signals are high, it means we are in the region of the screen in which we are drawing characters.

Typically we would store the character code to our 40 byte buffer when clk_counter is cycle#3, and load the pixel_shift_register at the end of cycle#7.

You might notice that in this diagram we only only shifting every second clock cycle. Here I am actually trying to show how we would shift the pixels during multi-color mode, in which each pixel is 2 pixels wide.

Implementing Multicolor Bitmap Mode

Let us now continue to implement Multicolor Bitmap Mode.

The following register bits are important to implement for this mode:

  • Register D011: Bit 5: Bitmap mode
  • Register D016: Bit 4: Multicolor Mode
  • Register D018: Memory pointers
To implement these register bits, we would follow more or less the same process as in previous posts, so I will not go into detail on how to implement these register bits.

Next, let us see how to retrieve and dispatch pixel data for Multicolor Bitmap mode.

We start by generating addresses for retrieving pixel data:

...
wire [13:0] bit_data_pointer;
...
assign bit_data_pointer = screen_control_1[5] ?
                   {mem_pointers[3],(char_line_pointer + char_pointer_in_line),char_line_num}
                 : {mem_pointers[3:1],char_buffer_out[7:0],char_line_num};
...

Here we cater for generating pixel data addresses for both Standard Text Mode and High Resolution mode, which is determined by bit 5 of Screen Control Register #1.

For Standard Text mode we use the character codes in screen memory to determine the address within the Character ROM.

In High Resolution mode, however, we use the pointer to the current location in Screen Memory to assemble the address for retrieving pixel data.

We also need to modify the code that shifts out the pixel data:

   always @(posedge clk_in)
   if (clk_counter == 7)
     pixel_shift_reg <= data_in;
   else begin
     if (screen_control_2[4] & (clk_counter[0]))
       pixel_shift_reg <= {pixel_shift_reg[5:0],2'b0};
     else if (!screen_control_2[4]) 
       pixel_shift_reg <= {pixel_shift_reg[6:0],1'b0};
   end


So, for multicolor mode we shift two bits at a time.

To create the actual multicolor pixel, we add the following statement:

always @*
  case (pixel_shift_reg[7:6])
    2'b00: multi_color = background_color;
    2'b01: multi_color = char_buffer_out_delayed[7:4];
    2'b10: multi_color = char_buffer_out_delayed[3:0];
    2'b11: multi_color = char_buffer_out_delayed[11:8];
  endcase


For bit combinations 01, 10 and 11 we use the value we previously retrieved from Color RAM and Screen RAM. It is therefore important that you buffer these values, so it is is available for the full 8 pixels.

Creating the Test Bench

Our Test bench for the VIC-II will look very similar to our existing C64 module's interface with the ViC-II.

Make sure that both the main 64KB RAM and Color RAM is connected to the VIC-II module. Both mentioned memories should also contain the image data of the splash screen, as we discussed earlier on.

Next we should write an initialisation block for setting the VIC-II registers so that it can show the Splash Screen in Multicolor Bitmap mode:

initial begin
  #50 we_vic_ii = 1;
  addr_in = 6'h11;
  reg_data_in = 8'h30;
  @(negedge clk_1_mhz)
  we_vic_ii = 0;
  
  #20; 
  we_vic_ii = 1;
  addr_in = 6'h20;
  reg_data_in = 8'he;
  @(negedge clk_1_mhz)
  we_vic_ii = 0;

  #20;
  we_vic_ii = 1;
  addr_in = 6'h21;
  reg_data_in = 8'h6;
  @(negedge clk_1_mhz)
  we_vic_ii = 0;

  #20;
  we_vic_ii = 1;
  addr_in = 6'h16;
  reg_data_in = 8'h10;
  @(negedge clk_1_mhz)
  we_vic_ii = 0;

  #20;
  we_vic_ii = 1;
  addr_in = 6'h18;
  @(negedge clk_1_mhz)
  we_vic_ii = 0;

  #20;
  we_vic_ii = 1;
  addr_in = 6'h20;
  reg_data_in = 8'hb;
  @(negedge clk_1_mhz)
  we_vic_ii = 0;

  #20;
  we_vic_ii = 1;
  addr_in = 6'h21;
  reg_data_in = 8'hd;
  @(negedge clk_1_mhz)
  we_vic_ii = 0;

end


Next, we should write an initial block for saving the pixel output of our VIC-II module to an image file:

initial begin  
  f = $fopen("/home/johan/result.ppm","w");
  $fwrite(f,"P3\n");
  $fwrite(f,"404 284\n");
  $fwrite(f,"255\n");
  i = 0;
  while (i < 114736) begin
    @(posedge clk)
    #2;
    if (!blank_signal)
    begin
      $fwrite(f,"%d\n", rgb[23:16]);
      $fwrite(f,"%d\n", rgb[15:8]);
      $fwrite(f,"%d\n", rgb[7:0]);
      i = i + 1;
    end
  end
  $fclose(f);
end

Here we create a PPM, where we store the pixel values in plain text. We precede  the pixel data with the information regarding the resolution of the image and the max value per color component.

The Final Result

Here is a picture of the final simulated result:


This is a bit of motivation that we are on the right track.

In Summary

In this post we implemented the Multicolor Bitmap Mode within our VIC-II module. As a simulation test we checked whether our Test Bench could create the loading Splash screen of the game Dan Dare.

In the next post we will link up our modified VIC-II module to our real FPGA and see if the Splash screen will also be shown when loading the game from the .TAP image.

Till next time!