Sunday 31 December 2017

Booting C64 on FPGA

Foreword

In the previous post we managed to boot the C64 system in a Verilog simulation.

In this post we will attempt to boot the C64 system on the ZYBO board.

Tweeking existing FPGA design

We need to make a couple of changes in order for the FPGA to boot the C64 system. The majority of changes for this we already did within the c64_core module. So, for our FPGA design we can just replace the code within our c64_core IP with the code we did in the previous post.

Above paragraph sounds like a mouthful, so how do we go about doing it? First, let us just refresh ourselves with how the block design looks like currently:


To get to this diagram, you double click on the block design within the sources tab with the project open in Vivado:

Right click on c64_core_0 and select Edit in IP Packager:


You will be presented with the same kind of Wizard as in a previous post where we wrapped the c64_core into an IP.

Apply the verilog changes and move to the last page of the Wizard (e.g. Review and Package) and click Repackage IP.

The IP repository will be updated, but your design will still have a old copy of the c64_core IP. For your block design to use the new version, you will need to delete it from the design and add it again.

You might recall from my previous post that we changed our module inputs and outputs of c64_core a bit. We changed the address output to an input and added another output for returning the data for given input address.

These changes to the inputs/outputs of c64_core necessitates changes to gpio_manipulator IP:

module gpio_manipulator (
  input wire [19:0] gpio_output,
  input wire [7:0] data_out,
  output wire [19:0] gpio_input,
  output wire clk_gen_rst,
  output wire rst,
  output wire debug_mode,
  output wire debug_clk,
  output wire [15:0] address_out
); 

assign clk_gen_rst = gpio_output[16];
assign rst = gpio_output[17];
assign debug_mode = gpio_output[18];
assign debug_clk = gpio_output[19];
assign address_out = gpio_output[15:0]; 
assign gpio_input[7:0] = data_out;

endmodule

With these changes made and all blocks in our block diagram been wired up again, our block diagram will look like the following:



With all this done, we are ready to run synthesis on our design again and to generate a new bitstream.

With Synthesis everything completed without an issue. However, my Vivado got stuck with an error during Bitsream generation. In short, this error told me that my fpga doesn't have enough components available to accommodate the synthesised design.

This error really puzzled me and I started to look for clues on what could cause this issue.

Eventually I found something that looked suspect: Whereas the Kernel ROM and Basic ROM utilised Block RAM, the 64KB RAM instance itself utilised distributed RAM resulting in trying to use every component it can find as a piece of RAM.

After further digging, I found the cause of the issue:

 always @ (posedge clk)
    begin
     if (WE) 
     begin
      ram[addr] <= ram_in;
      ram_out <= ram_in;
     end
     else 
     begin
      ram_out <= ram[addr_ram_in];
     end 
    end 


The issue here is that when writing to RAM one address input is used, and another address input is used when reading from it. This doesn't perfectly module a Block RAM in a Xilinx FPGA, which uses only one address input for both reading and writing.

This bug was in fact introduced in the previous post when we wanted to modify the 64KB RAM so that we can inspect the contents from a ARM Cortex program.

We can fix this bug by just using the addr_ram_in input for both scenarios.

With this fix our bitstream generation will complete without any issues.

Testing

Time to test the C64 bootup on our FPGA device. For this purpose we will again use a C program running on the ARM Cortex in the Zynq to evaluate the execution results.

Here is the complete C program:

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xil_io.h"
#include <unistd.h>

int main()
{
    init_platform();
    //Output: bit 16: Reset clock system
    //Output: bit 17: Reset C64 core
    //Output: bit 18: Enable Debug mode
    //Output: bit 19: Debug Clock
    //Output: bit 15-0: Address within 64KB RAM that we want to read
    //Input: bit 7:0: Data output for given address as specified in previous line
    print("Hello World\n\r");
    u32 regval = (1 << 16) | (1 << 17);
    Xil_Out32(0x41200000, regval);
    usleep(1000000);
    regval = ~(1 << 16) & regval;
    Xil_Out32(0x41200000, regval);
    usleep(1000000);
    regval = ~(1 << 17) & regval;
    Xil_Out32(0x41200000, regval);
    usleep(20000000);
    regval = regval | (1 << 18);
    Xil_Out32(0x41200000, regval);
    for (int i = 1024; i < 1500; i++) {
     regval = regval & ~0xffff;
     regval = regval | i;
     Xil_Out32(0x41200000, regval);
     usleep(10000);
     regval = regval | (1 << 19);
     Xil_Out32(0x41200000, regval);
     usleep(10000);
     regval = ~(1 << 19) & regval;
     Xil_Out32(0x41200000, regval);
     usleep(10000);
     u32 in = Xil_In32(0x41200000);
     in = in & 0xff;
     printf("in %x\n\r",in);
     usleep(500000);
    }
    cleanup_platform();
    return 0;
}


To aid in making things clearer I have added explanatory comments in the beginning for the different bit positions.

We start off once again by resetting the clock system and the C64 core after which we wait about 20 seconds.

After the 20 second wait we display the contents of the first half of screen memory pausing half a second for each value. It is important to note that since we are in the debug mode at this time, we are responsible for clocking the 64KB Block RAM from our program at each read.

When our program starts outputting the values of screen memory, you will see a couple of 20's (e.g. hex value for screen code space). Keep watching, since you will eventually see the screen codes for the welcome message:


The four 2a's corresponds to the four asterisks of the welcome message.

It appears that our FPGA device managed to boot the C64 system successfully!

In Summary

In this post we managed to boot the C64 system on the Zybo board from the simulation sources of the previous post.

In the next post we will explore how to interface with the SDRAM that comes equipped with the ZYBO.

Having limited Block RAM available on the FPGA, SDRAM access opens new horizons for us like buffering the output video frames and resizing it so it appears properly on an LCD screen.

Till next time!

No comments:

Post a Comment