Tuesday, 21 November 2017

Simulating a complete 6502 system


In the previous post we covered some Verilog basics.

In this post we will be developing the top module surrounding the 6502 core written by Arlet Ottens.

In the top module we will basically interface the 6502 core with a 64KB memory image of Klaus Dormann's Test Suite.

We will then run Klaus Dormann's Test Suite and see if all tests succeeds.

In this post, however, we will not be running our implementation directly on the FPGA, but rather in a simulation.

We will be using the simulation tool included in the FPGA development IDE from the vendor, called Vivado.

Installing Vivado

This blog assumes you have installed Vivado.

If you don't have Vivado you start by downloading install via this URL: https://www.xilinx.com/support/download.html

On the web page ensure that are are on the Vivado tab:

Scroll down to Full Product Installation and select the applicable download for your OS:

A web installer will download which allows you to select the components you want to install. Some components are free, while other are evaluation only.

For the purpose of this blog series the free components will do.

As part of the install ensure that Xilinx SDK is selected. This will be used to develop software running on the ARM Cortex processor.

Just a final note. As part of the installation you might be required to have a Xilinx user account. You can register on the Xilinx website and it is free!

Configuring Vivado for ZYBO

Next, let us configure the installed Vivado for use with the ZYBO board. Strictly speaking it is not necessary for this post, but since we are at the point of configuring Vivado, let us do all configuration in one go.

Firstly, we need to get the board file for the ZYBO board. This can be downloaded via the following link:


When you open the downloaded zip file, you will see a folder vivado-boards-master. Go into this folder.

You will see two folders, called new and old.

The old folder is folder is for pre-2015.1 Vivado versions. The folder that we will be using, is new, which is for Vivado versions 2015.1 and above.

Within the new folder you will see another folder called board_files. This folder corresponds to the following folder your within your Vivado installation:

{Vivado installation folder}/2017.1/data/boards/board_files

The part in your path 2017.1 might be different, depending on the version of Vivado you have.

Now, copy the contents of the folder board_files in the zip file to the board_files folder in your Vivado installation.

If you now restart Vivado, the ZYBO board configurations will be available in Vivado installation.

Creating a new Project in Vivado

Time to create a Vivado project for our 6502 simulation.

On the Vivado home screen select Create Project.

You will be presented with a wizard of pages.

On the first page click next.

Give your new project a name and click next.

On the next wizard page, Project Type, leave defaults as is and click next.

The next wizard page is where things starts to get interesting. When you click on the boards button, you will see a list of known boards your Vivado installation supports. Among them you will see a list of Zybo boards:

The list of Zybo boards you see in the list is the extra list of boards that appeared due to the set of steps you did in the previous section.

For simulation not that important, but when running your design on the FPGA it crucial that you select the ZYBO board corresponding to your board revision. The listed Zybo boards are very similar to each other. However, there is very subtle differences among the DDR-RAM parameters among them.

This subtle differences in DDR-PRAMETERS can just be enough to send you on a wild goose chase! I was one of those that ended in a goose chase for a week :-) I started off a project selecting Zybo Z7-10 (B.2) instead of the Zybo B.3.

With the incorrect board selected the ARM processor manage to start-up, but after executing a couple of machine code instructions, some memory location would just be modified out of the blue, causing chaos.

Anyway, back to the plot. With your board selected, click next. You will reach the summary page at which you need to click finish. In a minute or a new project will be created for you.

In the sources panel select the big '+' button to add some sources to your project:

On the first wizard page ensure that Add or create simulation sources is selected and click next.

On the next page we first need to add the two files from Arlet Ottens's core, called cpu.v and alu.v. So, click the Add Files button, and browse to these two files respectively on your hardrive, and add them.

Finally, it is neccessary to create a new file in which will create our top module. For this click the Create File module. For the filename, please specify top.v. Now hit OK and then finish.

The request files will now be added and created. After finished, your source panel will look similar to the following:

For top.v, Vivado has created an empty skelleton for you:

We will populate this skeleton in the next section.

Writing the top module

Let us now populate our top module with some code.

Firstly, let us instantiate an instance of the cpu module:

cpu  mycpu ( .clk (), 
            .reset (), 
            .AB (), 
            .DI (), 
            .DO (), 
            .WE (), 
            .IRQ (), 
            .NMI (), 
            .RDY() );

For now, all the signals is unconnected. We will connect them as we go along.

The specifics for the clock signal is defined as follows:

reg clk = 0;
always #10 
clk <= ~clk;

In the always block we are creating the pulsing clock for simulation purposes. Every time when the always block executes, it first wait 10 simulation cycles, indicated by the #10, and assign the inverse of the current clock state as the new value for clk.

Next, let us tie up the reset line:

reg reset = 1;
initial begin
  #50 reset <= 0; 
  #10000000 $stop;

We start the simulation with the reset line asserted.

The value of the reset line we change within an initial block. An initial block is a initialisation block that if called just once at simulation startup. It should also be noted that initial blocks is specifically for simulation and most of the time it would not even synthesise to anything at all on the FPGA.

Let us look a bit closer to the contents of this initial block. The initial block waits 50 simulation cycles (i.e. 5 clock cycles), before setting the reset line to zero.

Our initial block has one more purpose: Killing the complete simulation after 10000000 simulation cycles.

The simulation then runs for 10000000 simulation cycles, before killing the complete simulation with the $stop directive.

The next couple of lines of the cpu core, AB, DI, DO and WE are all part of a memory interface. So next, let us spend some time for designing this memory interface.

First, we instantiate our 64KB memory array:

    reg [7:0] ram[65535:0];

This register is a bit different from our previous register declarations. It basically states that 65536 registers should created (indicated by [65536]) and each register should have eight bits (indicated by [7:0])

Now, let us define the necessary memory interface wires and a always block for assignments:

wire [15:0] addr;
wire [7:0] ram_in;
reg [7:0] ram_out;
 always @ (posedge clk)
  if (WE) 
   ram[addr] <= ram_in;
   ram_out <= ram_in;
   ram_out <= ram[addr];

The always block closely models how a block ram element work and when doing synthesis, the tool will also pick up that want a block ram with this always block. It will then perform the synthesis accordingly.

With everything defined, we can now connect all the signals within our 6502 instance:

cpu  mycpu ( .clk (clk), 
            .reset (reset), 
            .AB (addr), 
            .DI (ram_out), 
            .DO (ram_in), 
            .WE (WE), 
            .IRQ (1'b0), 
            .NMI (1'b0), 
            .RDY(1'b1) );

For now we are not interested in the IRQ, NMI and RDY signals. For each of these signals we just choose a constant value that won't impede the operation of the CPU.

Almost done coding our top module. What remains to be done, is to populate our ram array with  a image of Klaus Dormann's Test Suite.

There is two verilog directives that help us out with ram array population:

  • readmemb: Read contents from a binary file and populate ram array with contents
  • readmemh: Read contents from text file with hexadecimal strings and populate ram array with contents.
readmemb is probably first price to use, because you can use the test suite binary as is. However, in a couple of cases I found that Vivado tools doesn't work so nice with readmemb. When trying to do the 6502 simulation with a readmemb, my Vivado IDE ended up in a endless loop. Using readmemh, however, I didn't experienced such issues.

The readmemh directive expects a text file with one hexadecimal number per line. Such a file is quite easy to create with the aid of a hexeditor.

With the binary file open in a hexeditor, copy the contents of the left panel (e.g. hexadecimal view) and paste into a text editor:

With the hex data in a text editor, you can replace each space with a newline.

There is one change you need to make to your hex file. The reset vector should be modified to start at 0400. Do this by changing the contents of memory locations fffc and fffd to 0 and 4 respectively.

With the hex file created, you can now add the following initial block within you top block:

initial begin
  $readmemh("{path to your hex file}", ram) ;

You need to replace {path to your hex file} with the applicable path to your hex file on your local drive.

Running the simulation

We are now ready to run the simulation.

On the left panel of the Vivado IDE, click run simulation and on the popup click Run behavioural simulation.

You will see a progress box running for a short while and then a wave window will open:

You will see that only a small time period of your simulation has run. This is due to some default Vivado settings. You can override this default if you want to, but most of the time it useful to see a quick snapshot of your simulation before running the full one.

You can resume the full simulation by click the play button on the top bar (labled run all).

You will see the display of the wave window updating while the simulation is running.

Running the Klaus Test Suite within a Verilog simulation can take ages to run. So, for now we just want some kind of idea that our 6502 environment is running more or less fine, and leave the complete test suite for the FPGA itself.

Here is some example of some sanity checks you can do:

On the wave output you do some spot checks on whether the ram give out the correct data for given addresses. remember that the output is only available in the next clock cycle for block rams as indicated by above diagram by the red lines.

This concludes our simulation exercise for this post.

In Summary

In this post we build the top module surrounding Arlet Ottens's 6502 core.

The top model contained interface to a RAM array populated with Klaus Dormann's Test suite code.

We ended off by running a simulation in Vivado and doing some spot check on whether the RAM is returning correct data for given addresses.

In the next post we will start with the physical FPGA implementation of top module we developed in this post.

Till next time!


  1. I'm reading your guide, but I think would be interesting have top.v source code complete to understand for a noobie like me;)

  2. [Synth 8-3848] Net addr_out in module/entity C64Core does not have driver.
    is normal?

    1. Hi Lorenzo

      Thanks for your comments and sorry for the late reply from my side.

      From time to time during Synthesis I also encounter this warning about missing drivers. I have read a couple of posts on Xilinx community forums and it seems like something you don't really need to worry about.

      Just on the source code. I have created a repo on github for hosting the source: https://github.com/ovalcode/c64fpga