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!

No comments:

Post a Comment