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!