FPGA meets 6502

So I’m working on designing my own homebrew 6502-based microcomputer. I have a lot of ideas for features I’d like, but before I make too many crazy decisions I’d like to solidify my understanding of the processor I’m building the whole system around. To this end, I am grabbing my Arty-A7, a W65C02S mounted on a breadboard and a bag of jumper cables to wire them together.

Planning Phase

I chose to use my Arty-A7 FPGA board because it has a ton of I/O pins, it can easily meet my performance requirements and because I just plain love tinkering with FPGAs.

This will take a smidge of organization to get started. I began by taking an inventory of the various pin functions on the W65C02S package.

Pin Diagram for W65C02S from Western Design Center Product Documentation

Generally speaking, there are 16 pins for the address bus, 8 pins for the data bus, 13 control pins, 2 power pins and 1 no-connect. This means I’ll need to wire 37 IO pins + 2 power pins to my FPGA board. With that in mind I’ve decided to use a mix of the PMOD interfaces on my Arty board as well as the Arduino-shield pins next to them. Each PMOD has 8 IO pins so I’ll use 1 PMOD for the data bus and 2 for the address bus, with the control pins on adjacent shield header. I’ll pull power from one of the PMODs as well.

Rough Pin Planning

With my rough plan in place, I took some notes on how my 6502 pins will map to the specific pins. I planned for which PMOD pin each bit of the address and data buses will go, along with associating each control pin to a numbered shield pin. Now for the fun part!

Wired Up

Now that the rough planning is complete, I carefully use the wire jumpers to connect everything as planned. I wired one pin at a time roughly starting from the top of the 6502 and working down, it’s a bit of a mess but it does the job.

The resulting mess of wires

Now that the plumbing is in place, the real fun begins. I start by opening up Vivado and begin getting these pins mapped into a design. I’ll create a new top.sv file to kick off the design.

`timescale 1ns / 1ps

module top(
// Arty stuff
input logic clk_100mhz,
input logic reset,

// 6502 stuff
input logic [15:0] address,
inout logic [7:0] data,
input logic vpb,
input logic phi1o,
input logic phi2o,
input logic rwb,
input logic mlb,
input logic sync,
output logic resb,
output logic sob,
output logic phi2,
output logic be,
output logic irqb,
output logic nmib,
output logic rdy
);

endmodule

The next step is to tediously map the various pins I planned out to the pins on the pins I’ve defined in my top module. The PMOD pins are easily found in the reference wiki for the board, but I didn’t see the IO shield pins on the primary documentation page, so I referred to the provided schematic files for that information. With all that pin mapping information, I added the pins properties into a constraints file.

A portion of the XDC Constraint File

At this point, everything looks to be ready for tinkering. As an initial experiment to validate that it is working, I’ll use Vivado’s VIO (Virtual Input/Output) IP block to manually probe and interact with these pins. Though, just before I generate that, I’ll add a few more lines to my top.sv to support bi-directional use of the data bus. For the 6502, when the rwb pin is high, the CPU is reading from the data bus, so I’ll use that as the trigger to put my write_data on that data bus during those times.

  logic [7:0] write_data;
assign data = (rwb) ? write_data : 'bZ,
sob = 1,
be = 1,
irqb = 1,
nmib = 1,
rdy = 1;

Once I am running on real hardware, I’ll be stepping through the reset logic for the 6502. So for this project irqb, be, nmib, rdy and sob can all stay at 1 for now, as I won’t be using them.

Now seems like the time to start building the VIO block. I’ll add ports for the various inputs and outputs, including the register I just added for bi-directional communication on the data bus.

I’ll let Vivado synthesize the core, and I’ll add it to the top module.

vio_0 debug_core (
.clk(clk_100mhz), // input wire clk
.probe_in0(address), // input wire [15 : 0] probe_in0
.probe_in1(data), // input wire [7 : 0] probe_in1
.probe_in2(vpb), // input wire [0 : 0] probe_in2
.probe_in3(phi1o), // input wire [0 : 0] probe_in3
.probe_in4(phi2o), // input wire [0 : 0] probe_in4
.probe_in5(rwb), // input wire [0 : 0] probe_in5
.probe_in6(mlb), // input wire [0 : 0] probe_in6
.probe_in7(sync), // input wire [0 : 0] probe_in7
.probe_out0(write_data), // output wire [7 : 0] probe_out0
.probe_out1(resb), // output wire [0 : 0] probe_out1
.probe_out2(phi2) // output wire [0 : 0] probe_out2
)

Now it is time for the moment of truth!

Playing with a Running CPU

With all that in place, it’s time to build everything and start playing around. After running synthesis, implementation and generating a bitfile, I can flash my new design and debug core to the Arty board and pull up the VIO interface that shows me the current state of things.

VIO showing probe states

At this point a bit more knowledge on the how the 6502 operates is necessary.  The phi2 signal is the input clock for the CPU. The core of this 6502 is fully static, so this clock can be stopped at anytime and the state within the CPU preserved. This allows me to interact and step the clock manually through VIO, even though my manual input will be extremely slow for interacting with hardware.

Since the clock looks good, I’m going to perform a reset of the 6502. Near the end of the reset cycle I’m expecting the address bus to read for the reset vector, a pointer to where the CPU should begin execution after reset. For the timing requirements on getting a reset to work properly, the documentation from WDC states specifically what to do:

3.11
Reset (RESB)
The Reset (RESB) input is used to initialize the microprocessor and start program execution. The RESB signal must be held low for at least two clock cycles after VDD reaches operating voltage. 

When a positive edge is detected, there will be a reset sequence lasting seven clock cycles. The program counter is loaded with the reset vector from locations FFFC (low byte) and FFFD (high byte). This is the start location for program control. RESB should be held high after reset for normal operation.

Western Design Center, Inc., W65C02S Datasheet

So I’ll hold reset low for at least 2 cycles, then set it back to high. I’ll continue to manually cycle the clock until I see CPU begin to fetch the RESET vector. As that vector is read, I will manually give it the address 0xDEAD to begin execution at. Then, to keep it running I’ll give it the opcode 0xEA (NOP) so it continues to read empty instructions over the bus. It’s worth noting that the 6502 does most things in alignment to the falling edge of the phi2 clock.

The 6502 has been reset and is running the instructions it’s reading from the FPGA! In the future I’ll extend this design to better simulate an imaginary system. That’ll be my stopping place for today, if you have any questions or feedback please leave a comment!

How to Quickly Pick Up Python

This is a quick post highlighting the basic features of Python, highlighting some topics to help experienced developers understand python quickly.

Basic Project/Code Management

In my opinion Python works best on Linux, though people using it on Windows or OSX can also work it just fine. The best practice is to use Python 3 whenever possible, while knowing some things about 2.7 just in case. The biggest difference you’d see right away between 2.7 and 3 is that print() is a function in 3, but a language keyword in 2.7

Pip is your best friend, it’s the package manager for python and is one of the best things about Python. You can install just about anything with pip install <package>. This is often used in conjunction with virtualenv to isolate package environments. A common practice is to create an environment with virtualenv, then install some packages with pip and get started on your project.

Here’s how I start many projects

virtualenv venv creates a virtual environment in a directory named venv (this is what many python devs use as a convention). The environment is entered by sourcing the script in <environment>/bin/activate. When you want to exit it you can use the alias deactivate from within the virtual environment. Within this environment, all packages are installed in this isolated area.

Another convention for a project is to use a requirements.txt file to track libraries. You can get a list of the current environments’ packages with pip freeze. You can pipe the output of this command to a requirements.txt file so that another machine or user can pull down dependencies with pip install -r requirements.txt.

For managing python code in a git repo, you’ll want to add *.pyc and venv/ to your .gitignore to ignore cached python code files and your virtual environment.

Learning the Syntax and Dabbling

As you probably already know, Python is interpreted and spacing is part of the syntax. Rather than using curly brackets to contain block scope, Python uses levels of indentation. Tabs are permitted but most Python programmers will use 4 spaces for tabs. Python has a culture of appreciation for tidy code, pep8 can do some basic format nit-picking for you.

IPython is a handy tool for playing with basic syntax and exploring code and libraries.

The basic data types you’ll mess with in python are int, float, bool, str, object, list and dict.  Nearly everything in Python is implemented by a PyObject, and Python has a rich set of OO features that are mostly provided by that underlying object.

Dabble through the official Python Tutorial to practice, it’s a relatively quick but thorough avenue to learn the basic syntax, control flow, how to define functions and classes, and it explores the included standard library.

Getting Things Done

Here are some resources for specific topics from the Python Tutorial and Manual:

Beyond the included libraries, there are many other incredibly useful libraries, this is certainly not close to a comprehensive list but these libraries are tried and true:

Hopefully this can serve as a quick reference for those interested in adding Python to their repertoire. If you have any feedback or comments please drop a comment!

Spawning New Linux Processes in C

There are many good reasons to spawn other programs in Linux to do your bidding, but most programming languages don’t give you nearly as much control of the process as in C. In this post I will cover some of the most common ways to create new processes and manage them in C on a Linux system.

Grab a Fork

The classic fork() function is the most popular way to create a new process. It works by duplicating the process that calls it. It may sound complicated but it’s a fairly simple system.

#include <unistd.h>

pid_t fork(void);
#include <unistd.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
  pid_t fork_pid = fork();

  if (fork_pid == 0) {
    printf("Hello from the child!\n");
  } else {
    printf("Hello from the parent!\n");
  }

  return 0;
}
$ ./forktest 
Hello from the parent!
Hello from the child!

When a process calls fork(), Linux will duplicate that current process. The value returned by fork() will be 0 in the child process. In the parent process fork() will return the PID (Process ID) of the new child process or -1 if some error occurs.

Replacing a Running Process

After creating a new process, it’s common to replace that child process with an entirely different program. The exec() family of functions can handle this for us. The execl() is the simplest method.

#include <unistd.h>
int execl(const char *filename, const char *arg, ...);

All you need to provide is the location of the file to load, and the arguments you’d like to provide to it. Just like for any normal process, the first process is the process name.

#include <unistd.h>

int main(int argc, char *argv[]) {
  execl("/bin/bash", "/yes/its/bash", "-c", "echo $0 && uptime", NULL);
  
  return 0;
}
$ ./execltest
/yes/its/bash
10:11:12 up 1:07, 2 users, load average: 0.07, 0.10, 0.18

The other functions in the exec() family have various options to control arguments and environment variables for the new process that takes over.

Playing with the Kids

After you have wee ‘lil child processes, you’ll probably want to make sure they are doing as they are told. After you’ve sent the child process off to do its chores, you can use the wait() function to see what it returns with after it’s done.

#include <sys/wait.h>

pid_t wait(int *status);

When you call wait(), it will block the parent process until any of it’s children processes change state. The status pointer can be used if you’re interested in knowing what kind of state change has occurred, to determine if the program exited normally, or if it was terminated by a control signal. The return value of wait() is the child PID that has changed states.

#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>

int main(int argc, char *argv[]) {
  pid_t child = fork();
  
  if (child) {
    wait(NULL);
    printf("child process terminated\n");
  } else {
    execl("/bin/bash", "/THECHILD", NULL);
  }
  
  return 0;
}
$ ./waitkids
$ echo $0
/THECHILD
$ exit
exit
child process terminated

Check the man page for wait for more details on the various options available when waiting.

Attack of the Clones

The fork(), exec() and wait() families of functions are portable across POSIX compliant systems. If more fine grained control over the process creation is desired, we’ll need to use the Linux specific clone() function.

I won’t cover the clone() in this post, as that’s probably more suited for a dedicated post. Regardless, I suggest perusing the man page for it to get an idea of what capabilities it offers.

That covers the basics of process creation using C on Linux. In the next post I plan on covering some of the methods available to communicate between running processes. If you found this helpful or informative, or have any feedback, please leave a note in the comments!