Sunday, July 3, 2011

A simple experiment with JSSim (visual6502)

The folks at visual6502.org have really done a great job on their project and I've been meaning to get more familiar with their work for a while. Now that I graduated I have more time for these projects and was able to dig in over the past week and especially yesterday. Bottom line: my experiment can be found at http://johndmcmaster.github.com/visual6502/ and a demo at http://johndmcmaster.github.com/visual6502/tutorial-inverter.html This writeup is based on my git commit 6a613ee1131bbdec9a8bf4b6eeb02d13147842ab which was forked from mainline's de265ecdb89d8c5d299f09ad69aaf8b87b1aed5d. Changes are as noted, but most code snippets are copyright Brian Silverman, Barry Silverman, Ed Spittles, Achim Breidenbach, Ijor and maybe some others that I missed. See the github source for details.

I don't have much experience with JavaScript but I have enough experience with C like languages that it isn't really hard to use and just try to follow the syntax of things around me. I started by moving the 6502 into its own folder like later chips have been tending to so that I could focus on the relevant files easier. For those not familar with visual6502, here's a screenshot of the 6502 running in JSSim (Java Script Simulator):


Although its not obvious from the still picture, metal, poly, and diffusion are being colored according to their voltage potential. Wow! An outstanding way to learn about chips. However, the complexity of the simulator scared me from really trying to understand how it worked. Fortunately, most of the work is put into the data and the simulator core is easy to follow. In this post I'm going to step you through how visual6502 works and how to create a clocked inverter circuit using simple tools.

The first thing that you'll need is a reference diagram. I somewhat arbitrarily decided to try an NMOS inverter since I knew the 6502 was NMOS logic and could look at an example if I got stuck. An inverter just seemed like something I could easily clock with a single input. Lets start with a brief review of NMOS logic since these days its all about CMOS. In NMOS logic, we use a single transistor polarity and short out voltage through transistors to invert outputs. Here is an NMOS inverter from Wiki:
When A is 0 the switch is open and current can flow from VDD through R to OUT (A: 0, OUT: 1). If we put voltage on A (gate) the switch is closed and shorts out OUT (A: 1, OUT: 0) through the drain at top and source at bottom. NMOS was discontinued because CMOS didn't have the issue of needing to short out a resistor (power consumption when input is 1), eventually became faster (better noise margin), and also took up less chip space.

Converting this into a (simplified) layout:
I used gray for metal, black for contacts, red for poly, green for N diffusion, and white for P substrate. The blue lettering is an arbitrarily assigned net (electrically connected group) number which we'll use later as we convert this into a simulatable data file. I might use the term node and net interchangeably, they mean the same thing here. As a reminder, the distinction between source and drain is less important at a fundamental level. For our purposes we only care that the ends of the green blocks are the switch contacts and that the switch is controlled by the red part (polysilicon aka poly). Finally, assuming self aligned gates, the poly protects the silicon under the gate and so we only have diffusion around the poly and not under it. Early CMOS used metal gates but later switched to poly (not regular Si because you can't grow good crystals on an amorphous SiO2 glass surface).

Notice that we really try to avoid conventional resistors. While they can be made from strips of poly or diffusion, the easiest way is to make them out of transistors. I am not deeply familiar with this and initially had the drain and gate connected instead of the gate and source as above. So if you see images with them reversed its because I was too lazy to re-take screenshots after I fixed it. On my TODO list so that I can better recognize and understand them. The transistor below is more interesting and we'll mostly focus on it.

Pretty picture, but its also pretty lifeless. Time to start digging into the codebase. If you grab a copy of the visual6502 source code (either from my repo listed above or from the main repository at https://github.com/trebonian/visual6502) you should see chip-6800 subdirectory which defines the files you'll need to create for your own simulation:
  • nodenames.js: defines human friendly node names such as clk0
  • segdefs.js: defines how to draw the non-transistor parts and their connections
  • transdefs.js:transistor to net connections and transistor drawing
  • support.js: utilities and overrides to stub out unneeded functions
  • testprogram.js: CPU instructions. Since we won't have a CPU we don't need this file

nodenames.js contains the nodenames variable and looks something like:
var nodenames ={
gnd: 2,
vcc: 1,
clk0: 3,
}
vcc is net 1, gnd is net 2, and clk0 has been aliased to net 3.

segdefs.js contains the segdefs variable and looks something like:
var segdefs = [
[ 4,'-',5, 177,94, 193,95, 193,179, 178,180],
[ 1,'+',4, 128,214, 177,214, 177,265, 129,264],
[ 2,'-',3, 128,95, 179,94, 177,146, 128,146],
[ 4,'-',0, 66,163, 192,161, 193,179, 64,179],
]
Which probably looks pretty cryptic at first glance. The first element is the node number. The second is the pullup status: '+' for pullup and '-' (although I think any non-'+' value will work) for regular. That is a '+' indicates a resistor is connected to the positive supply and will turn on attached gates if not shorted out. Each pair thereafter forms part of the polygon used to draw the chip. All of the above are rough rectangles.

The next number is the layer number. This does not effect the simulation to my knowledge but we do want the visual aspect to work correctly. If you look in expertWires.js you should see:
var layernames = ['metal', 'switched diffusion', 'inputdiode', 'grounded diffusion', 'powered diffusion', 'polysilicon'];
var colors = ['rgba(128,128,192,0.4)','#FFFF00','#FF00FF','#4DFF4D',
'#FF4D4D','#801AC0','rgba(128,0,255,0.75)'];
var drawlayers = [true, true, true, true, true, true];
Which defines the layer numbers (0 indexed). Thus the sample data above defines the layers poly, powered diffusion, grounded diffusion, and metal. Switched diffusion is diffusion that will change state during simulation because its on a switched side of a transistor. In the sample image the two diffusion segments on the right are switched since they may or may not have a voltage potential on them depending on whether the transistor is on. The upper left diffusion is powered since it always has positive voltage and the lower left is grounded diffusion since its always at ground potential. Hopefully poly and metal are self explanatory.

We render in the order given, so make sure to place them in a good order. Make metal last as its semi-transparent and anything else will just cover it up. None of the other polygons (except transistors, but they aren't usually rendered) should overlap but if they do just arrange things as needed.

The final key file is transdefs.js which contains the transdefs variable:
var transdefs = [
['t1',4,2,3,[176,193,96,144],[415,415,11,5,4566],false],
['t2',1,1,3,[177,191,214,265],[415,415,11,5,4566],true],
]
The first element is the node name which is followed by the gate, first, and second net connections respectively. Like in the layout we don't distinguish between the gate and drain.

Now that we know what data we need we need to generate it. While I could learn to use or develop my own tools for converting layers to *.js files, I decided to go with the KISS strategy. I used the Kolourpaint toolchain to generate my *.js files:


I generated the points by hovering the mouse over the various coordinates and typing them into the *.js files. With both windows open at once it went pretty quick. If you're wondering why its upside-down, its because the simulator has the origin in the lower left hand corner and kolourpaint has it in the upper left hand corner. By flipping upside-down the coordinates come out correctly.

But its not over yet. I've glazed over utils.js but its actually necessary for this to work. The stock functions are more specialized for a full blown MCU, a 6502 in particular, and we will have to override these functions as appropriate. Finally, we need to set the canvase size by setting grChipSize which sets width and height. My images were 400 X 400 so I set grChipSize to 400. Lets step through initialization so that we know what we need to fixup.

We start in the main .html file by including a bunch of stuff. In particular you'll need to change the paths to reflect your files instead of the template's. For example, I used chip-6800 so had do substitute things like:
<s cript src="chip-6800/segdefs.js"></script>
for
<s cript src="chip-tutorial/inverter/segdefs.js"></script>
or whereever you put your files. Trusting the general structure and skipping over the HTML layout, the key item is
function handleOnload() {
...
setTimeout(setup,200);
...
}
Which launches setup() in expertWires.js after 200 milliseconds. The other key item in the main file is the play button:
<a href ="javascript:runChip()" id="start"><img class="navplay" src="images/play.png" title="run"></a>
which calls runChip(), but we won't worry about this for now.

This function is mostly just a bootstrap for the next stage. They do a lot of this and I'm not sure why they don't just make function calls.
EDIT: I've been told this is related to not letting scripts run too long and make the browser complain. By re-submitting the request the browser doesn't get so angry. They aren't sure if this is standard for web services but it seems to work.
Anyway, here it is:
function setup(){
statbox = document.getElementById('status');
setStatus('loading ...');
setTimeout(setup_part2, 0);
}
And this gives:
function setup_part2(){
frame = document.getElementById('frame');
statbox = document.getElementById('status');
setupNodes();
setupTransistors();
setupParams();
setupExpertMode();
detectOldBrowser();
setStatus('loading graphics...');
setTimeout(setup_part3, 0);
}
setupNodes() works on segdefs to setup the visual portion. For historical reasons (in a comment I read somewhere) it also contains the pullup status as noted earlier.

setupTransistors() does the actual transistor and net setup. One point of interest is that C1 will become "interesting" if C2 is but C1 isn't (ie GND and VCC will be moved to C2 if they weren't in transdefs.js). We also build a list of all of the transistors connected to each net. That way when we simulate an event we only have to reference the net instead of iterating through all of the other transistors looking for relevant gates by exchanging memory for CPU usage.

setupParams() parses query parameters (page.html?key=value) and so isn't important for basic usage. setupExpertMode() sets up the probe control panel and you don't really need to worry about it. Finally, detectOldBrowser() is compatibility related (makes rendering faster on certain systems?) and you also don't need to worry about it.

We now move onto setup_part3():
function setup_part3(){
if(chipLayoutIsVisible){
updateChipLayoutVisibility(true);
}
setStatus('resetting ' + chipname + '...');
setTimeout(setup_part4, 0);
}
The chip layout should be visible and so we start to render the layout and move onto part 4:
function setup_part4(){
setupTable();
setupNodeNameList();
logThese=signalSet(loglevel);
loadProgram();
setupConsole();
if(noSimulation){
stopChip();
running=undefined;
setStatus('Ready!');
} else {
initChip();
document.getElementById('stop').style.visibility = 'hidden';
go();
}
}
Glaze over things and go to initChip() which is important since you'll need to define it. initChip() is responsible for setting the startup logic state. Unfortunately the default implemetnation in macros.js has statements like setHigh('rdy') which are 6502 specific. I cut that stuff out to give a very basic chip initialization instead. See my support.js, but basically it sets all transistors to off and then recalculates all transistors (recalcNodeList(allNodes())).

recalcNodeList() is a core interface. Its a discrete event simulator where we propagate switch information when things change. Since there's no guarantee it will settle, it will abort after 100 iterations if we did something dumb like create a ring oscillator by accident.

setup_part4 finished by calling go(). This will start looping the simulation. Usually this is by hitting the play button in main, but I hard coded the running variable to true so that I didn't have to hit the button. Also worth noting that I added a step delay variable (go_timeout). It may make sense for larger chips to run at full throttle, but for this simple simulation I limited at 1 Hz. step() will look for the net clk0 and invert its state. It also does a few other things so I added the following stubs:
/*
Simple logic chip stubs
*/
/*
Print registers and such, we have none
Could use the input and output pins I guess if we really wanted to
Used extensivly in macros.js
*/
function chipStatus(){}
//Simple logic chips have no bus to read/write, skip over
//Executed as part of clocking (halfStep()) in macros.js
//Alternativly we could have just re-implemented these functions
function handleBusRead() {}
function handleBusWrite() {}
//Stub implementation in case not using memtable.js
//No memory to track
function setupTable() {}
Whew! We should be ready to run. Check my data files for how I defined the*.js files. Allright, lets see what we get:




?!? Upon a little investigation, we see that there is a 400 pixel gutter. Since our image is 400 pixels, if we set grChipSize to 1200, we will see it centered at the bottom:


But really we want it to look nicer and so take of the 400 pixel left gutter:


Alternativly we could have made the transistors big enough so that the gutter doesn't matter. I added the variable main_area_left_gutter and set it to 0. I'm not clear why they added a gutter to the left but not bottom. In any case, lets see some clocking action! (The above image was taken before I added the clock) Clock on:


Clock off:


And it works! As you can see, the powered and grounded diffusion stay the same and the active diffusion area changes along with the metal. Not too much work overall even if you don't much about web technology.

Thanks to the visual6502 folks for providing such great software and making my inverter be more correct instead of "just work"! My next steps will be to start cross referencing the *.js against the die images and also to generate *.js automatically from layer images. On a final note, I've also learned a relatively simple technique for preparing ICs for live analysis that I'll hopefully make a post in the near future about.

2 comments:

  1. Nice work John and thanks for the clear explanatory write-up.

    (The call-backs which break up the setup functions are there to let the browser know the page isn't broken, if the script is taking a long time on a slow system.)

    ReplyDelete
  2. The main reason why it's better to use transistors instead of resistor is occupied space on die. High value resistors may take tens of percents of whole die.

    ReplyDelete