Overview
Hello! This is the Build Your Own Breadboard Computer (BYOBC) Book!
BYOBC is a framework for learning how computer systems work, by building one from individual components.
If you're looking to teach this course, see Teaching.
By the end of this book, you should intuitively understand:
- The fetch/decode/execute loop
- Address and data buses
- Timing constraints
- Memory mapped I/O
- Interrupts
- Using GPIO to interface with peripherals
- Serial communication with UART
- Basic 6502 assembly programming
And you will have a computer that might look something like this (though whether it looks cleaner than this is up to you!):
Prior Experience
If all of the above topics sounded like a lot, don't panic! This course is intended to be accessible even to electronics beginners. You'll be fine if you have experience with the following:
- Basic breadboard usage
- Any programming language (though lower-level is better)
More than anything else the biggest keys to success are patience, cleanliness, and debugging skills. We'll have tips on how to organize your computer and diagnose issues later!
Materials
If you're taking this as part of the 98-341 StuCo at Carnegie Mellon University, then good news! The materials will be provided.
If not, stay tuned for ways to participate at home. TODO: Materials listing.
For Instructors
If you're not looking to host this as a class, you can safely skip this page.
Course Design Choices
TODO: Rest of this page
why did i use the 6502? why breadboards?
Can I teach BYOBC?
Core Goals
BYOBC is centered around two guiding principles: accessibility and justification.
Accessibility
Materials
Name | Foobar |
---|---|
abcd | bcdfds |
Lecture Format
how many lectures should there be? how long should lectures be?
Class Size
how many students should be in a class?
L1 - Electronics Refresher
In this chapter, we'll be covering the basics of logic gates, the fundamentals of digital signals, and building a logic circuit on a breadboard.
Don't skip this chapter too hastily– it's essential to understanding how to modify and fix your computer!
Lecture
Logic Gates
All of modern computing is predicated on the bit, a single unit of information that can only ever be either 0 or 1.
By itself, a single bit doesn't do much. The power of computers comes when we combine bits together and do operations on them using logic gates.
The simplest gate (aside from the gate that does nothing) is the NOT gate, or inverter.
A | NOT A |
---|---|
0 | 1 |
1 | 0 |
TODO: The rest of the gates
Designing Some Logic
Let's say we wanted to implement the following truth table with some logic gates
(where Q
means our desired output):
A | B | Q |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 1 |
1 | 1 | 0 |
After some contemplation, we could probably deduce that Q
looks like A AND NOT B
.
So, if we have some AND gates and some NOT gates laying around, we might implement this as:
However, this is rather inconvenient. We'd need to wire up two separate kinds of gate (NOT and AND) to do this very simple function. Furthermore, most logic gate chips come with four or more gates on them, and the unused gates take up space on the breadboard even if we're not using them.
Let's make some modifications. First of all, we can recognize that, because AND and NAND are just inverses of each other, we can replace the AND gate with a NAND followed by a NOT:
Then, we can also replace the NOT gates with NAND gates:
And now we've successfully reduced our logic to only need one kind of gate, and since the NAND gate IC we'll be using offers four NAND gates per chip, we only need to use one chip.
Theory & Practice
When designing or analyzing the behavior of a digital circuit, keeping extraneous details hidden and keeping wires organized is a good thing. This is why typically we draw circuits as a schematic, where we use component symbols to represent the pieces of the circuit. The diagrams of the logic gates we used above were schematics.
However, when we go to actually build the circuit using the materials at hand, a number of problems appear. Most notably: our integrated circuit (IC) doesn't really look much like a NAND gate!
To understand the problem, we need to read the datasheet for our IC. In this case, we're using the 74HC00, whose datasheet is available on Texas Instruments's Website.
The format (and helpfulness) of a datasheet varies by device and manufacturer, but typically the information we need is on or near the first page. For example, we can see the name of the device ("SNx4HC00 Quadruple 2-Input NAND Gates"), we can see that it works at voltages between 2V and 6V (which is good, because we'll be using 5V), we can read a brief description of the chip's features, and most importantly we can find a pinout diagram at the bottom.
The pinout diagram reveals to us how the pins on the device function as NAND gates: each group of three pins is the two inputs and one output of a single gate. In the bottom left and top right corners are where we supply power to the logic gate.
Power
There are many kinds of transistors and so many kinds of ways to implement digital logic. This has resulted in a several possible notations for the power pins on ICs, which are mostly interchangable:
+5V, VDD, and VCC all mean the same thing (at least in this class).
0V, VSS, and GND also all mean the same thing (at least in this class).
Any time any of the above pins appears on an IC's datasheet, you should always make sure that you remember to connect them to the appropriate power supply.
Logic Levels
Up until now we've been discussing logic in terms of 1 and 0, but we all know that math isn't real. You can't send a number across a wire–you send a voltage!
If we charge a wire to a HIGH voltage (in our case, above about 4.5V), we interpret that wire as meaning "1".
If we charge a wire to a LOW voltage (in our case, below about 0.5V), we interpret that wire as meaning "0".
Any voltage in-between HIGH and LOW is meaningless, and most gates aren't designed to work correctly with voltages in the middle.
When dealing with abstract logic gates, it's much more common to use the 1/0 notation. When we're looking at voltages in a circuit, however, using HIGH/LOW is useful as a reminder that we're in the real world.
For the purposes of this class, we will assume that a HIGH signal means the same thing as 5V, and assume that a LOW signal means the same thing as 0V.
Floating Wires
In theory, any wire in a circuit should always be either HIGH, or LOW. This is because we try to design our circuits so that all the wires are always "driven" (strongly) or "pulled" (weakly) to be HIGH or LOW.
For example, if a wire is connected directly to the 5V/GND rail, we say that it is driven (or sometimes "tied", since it's just a wire) HIGH/LOW.
The output of a logic gate is always strong, so it drives signals, unless specifically noted otherwise.
If a wire is connected to (for example) the 5V/GND rail through a resistor, we say that it is pulled HIGH/LOW. By itself, this behaves the same as driving the wire HIGH/LOW; but the distinction becomes important later when we start mixing driving and pulling.
The diagram below shows an example setup. Wire A is being driven HIGH, wire B is being pulled LOW, and wire C is being driven HIGH.
What is very important is what happens if a wire isn't being driven or pulled at all: we say that the wire is floating, because it isn't (electrically) connected to a known voltage. In the above diagram, wire D is floating. Floating wires are almost always bad in digital circuits because, depending on your luck, they will switch between HIGH, LOW, and somewhere inbetween seemingly at random. Not only does this produce difficult-to-debug issues, but many logic gates will heat up and destroy themselves if they receive an input halfway between HIGH and LOW for too long.
In other words: wire's haunted.
The Probe
Unfortunately, tools like multimeters are ill-suited to finding out if a wire is floating. If you try to measure a floating wire's voltage with a multimeter, it will often say 0V due to imperfections inside the meter–in other words, we can't distinguish LOW and floating!
Fortunately, you have another means of checking your wires for floatiness: the probe. When the green light is on, the signal is HIGH. When the red light is on, the signal is LOW. If both lights or neither light is on, or the lights are dim, the signal is floating.
TODO: Insert image
The #1 cause of inconsistent behavior in breadboard circuits is because of floating wires, so always make sure you poke your wires and ensure they are being driven!
Hands-On Section
New Materials
- Breadboard
- Breadboard Wire Kit
- Male-to-Male Jumper Wires
- 74HC00 4x 2-input NAND Gate
- Debugger PCB
- LED
- Resistor
Assignment
Build the following logic, which implements "A and not B", on a breadboard. The debugger should be attached and used for power and for "THE PROBE."
The power rails should be connected to 5V/GND respectively, since they'll be needed later. Always turn off the power on the debugger when changing wiring, then turn the power on and check each part of the circuit using the probe.
Common problems include:
Power - Make sure the debugger is turned on and plugged in (it has a power LED).
Power Rails - All 4 power rails should be in the correct state (either HIGH or LOW).
Wrongly-Bent Pins - The NAND gate's pins should go into the breadboard holes that are directly straddling the gap in the center. If the pins are bent too far outwards, try gently rolling the NAND gate against the table on both sides evenly to bend the pins closer together.
Pinouts - The 74HC00 datasheet shows how the NAND gates are laid out inside the chip. Note that the side of the chip with the indent is always the top.
Logic Inputs - The A and B inputs should always be connected to either 5V or GND, never floating.
NAND Gate Power - Always ensure that the NAND gate's VDD and VSS pins are connected to 5V and GND, respectively.
NAND Gate Outputs - The output of each NAND gate should always be HIGH or LOW. If they are floating, then typically the pinout is wrong or the NAND gate isn't powered.
L2 - The NOP Computer
Today we're going to discuss the very basics of what a "program" really is, see how a CPU can execute programs, and finally make our CPU run the very simplest program we can possibly run.
Notation
The original version of the 6502 was released in 1975, and programming syntax wasn't nearly as standardized as it is now. For this class we'll be using the notation used by the 6502's documentation and all of the related tools, shown in the table below.
Name | C Notation | 6502 Notation |
---|---|---|
Decimal | 42 | 42 |
Binary | 0b101010 | %101010 |
Hexadecimal | 0x2A | $2A |
The important details is that %
means binary, and $
means hexadecimal.
Typically when writing large blobs of numbers (e.g. snippets of machine code) adding the $
hurts readability,
so this book will omit the $
for cases where there is no ambiguity.
A Computer
As discussed last time, we defined a computer as a thing that can run programs, where the program is simply a list of steps, or instructions. In our case, the instructions are just a list of bytes.
The very basics of a computer, then, can be boiled down into two parts: one part that contains the instructions to run, and another part that actually runs the instructions.
The part of the computer that actually runs the instructions is the central processing unit, or CPU. You likely already have heard of many varieties of CPU, made by Intel, AMD, Apple, Qualcomm, etc. It's important to note that a CPU by itself doesn't constitute a computer– without instructions to run, it can't do anything!
So our basic model of the computer starts out as two boxes: A CPU to run instructions, and storage to hold the instructions.
TODO: Insert diagram here
The Data Bus
A possibly-obvious problem with the two-box model is that our two boxes aren't connected in any way. Somehow, a series of instruction bytes in the storage need to make their way over to the CPU so that they can actually be run. Fortunately, we invented a convenient way to move information very quickly back in the 1800s: just connect the two boxes with some wires!
In our case, we use eight wires to connect the storage and the CPU, one for each bit in a byte. This collection of eight wires is called the data bus, and it can transfer a single byte of data at a time to or from the CPU. Every instruction the CPU will ever execute must, at some point, travel across the data bus.
TODO: Diagram
The Address Bus
With the addition of a data bus, our CPU can now retrieve instruction bytes from the storage. However, there's a problem: how does the storage know which byte it should be sending?
Consider the following pseudocode:
if virtual coin flip is heads:
print "hello"
else:
print "goodbye"
If the coin flip is heads, we need to be able to send over the bytes that will execute print "hello"
,
but if the coin flip is tails, we need to send over a different set of bytes that will execute print "goodbye"
,
and only the CPU knows which path is correct.
Thus, the CPU needs to be able to tell the storage what part of the program it wants next. This is done with a group of sixteen wires called the address bus. Unlike the data bus, which can sometimes be bidirectional, the address bus is always driven by the CPU.
For storage devices, the address bus is very similar to an array index.
The storage device will look at the address bus, and it will place on the data bus whichever byte is at that location/index.
If $0000
is driven on the address bus, the very first byte in the storage will eventually appear on the data bus.
If $0001
is driven on the address bus, the next byte of storage will eventually appear on the data bus, and so on.
TODO: Diagram.
The Program Counter and Registers
We now know that the CPU can ask for the next program byte by putting an address on the address bus, but how does the CPU know which address to ask for?
The answer is that the CPU has a tiny amount of storage inside of it called the program counter (or PC, for short) that always holds the address of the next program byte. The program counter is 16 bits wide, big enough for just one address, and will increment by one for each byte the CPU finishes reading from the program.
The program counter is an example of a CPU register. A register is a tiny amount of storage inside the processor itself that can be accessed rapidly. The 6502 has a handful of registers that we'll discuss later, when we start programming.
Fetch, Decode, Execute
The process by which the 6502 (and every CPU currently in existence) runs a program involves two-ish steps:
- Fetch: Read a program byte from storage
- Decode: Figure out what the program byte means
- Execute: Do what the program byte means
You may observe that I have listed three steps, not "two-ish." That's because the decode step happens entirely inside the CPU and kind of gets blended with the execute step; we can't control it and we don't really need to worry about it. So, from our point of view, we have:
- Fetch
- (Decode and then) Execute
As it turns out, this is why every instruction on the 6502 takes a minimum of two steps: first the CPU must fetch the byte for the instruction, and then it must do some action.
The Clock
TODO:
Reset
TODO:
Hands-On Section
Build
Build the above schematic on the breadboard. An example board is provided for your reference.
Keep in mind that clean wiring solves most computer problems!
Debugger Usage
To test our circuit, we can use the debugger. You'll need to download the debugger repository, and run a command inside:
python ./console/debugger.py debug
You may need to use python3
instead of python
,
and you may need to specify the serial port with --port
.
Once the debugger has started, the interface will look like the following:
RST
PC: 0000 A: 00 X: 00 Y: 00 P: 24 S: 00 seq_cycle: 1
ADDR: 0000
DATA: EA
STATUS: sync:0 rwb:1 (R) RESB:0 nmib:1 irqb:1 vpb:1 | PHI2: 0
>
From top to bottom:
RST
indicates that the 6502 is currently being reset.PC
indicates the value of the Program Counter. The values beside it indicate other registers.ADDR
shows the current state of the address busDATA
shows the current state of the data bus- The pin names on the
STATUS
line all correspond to identically-named pins on the processor.
Take note of the value of PHI2 (the clock) currently.
We can advance by half of a cycle (i.e. from low to high or high to low)
using the h
alfcycle command.
Type h
and press enter.
Observe that PHI2 changes.
The halfcycle command is useful,
but often halfcycles are too short--
most interesting behavior happens after a complete clock cycle.
The cy
cle command advances PHI2 to the next time it will be high,
because not much happens when PHI2 is low.
Type y
and press enter.
Some of the other values might change
because we've just stepped one clock cycle forward.
We'd like to step a few more cycles now.
We could type y
again, but there's a convenient shortcut:
pressing enter with nothing at the prompt will rerun the last command.
Press enter repeatedly to step and observe the sync
signal.
As soon as it becomes 1, stop:
we're about to fetch our very first instruction.
Observe the value of the address and data buses.
The data bus should show EA
(since, after all, that's how you connected the wires).
Step a few cycles and observe how the address bus and the sync pin change.
Based on this, how many cycles does the NOP
instruction require?
Once you're done, use the q
uit command to exit.
L3 - Program Storage
Lecture
This time we're going to be reviewing what really happened when our computer ran the NOP instructions last time, and move on to providing a real storage medium for the computer to hold more interesting programs.
NOP Computer Recap
TODO:
Today's Goal
Currently, our computer has only a single byte of program storage: the eight wires we connected directly to the data bus are the only byte that will ever run.
Most interesting programs consist of more than a single byte though, and in order to store those bytes we need another IC.
Storage Technologies
There are a few key characteristics of programs that dictate the way in which we store them:
- Most programs are not changed often (or changed at all).
- We typically want programs to persist even when power is turned off.
If we look back through history, these requirements were met in a number of ways (to name a few):
- Solid State Drives
- Hard Drives
- Installation CDs
- Floppy Disks
- Magnetic Tapes
- Punch Cards
Unfortunately, these methods all suffer from a significant drawback: they're complicated. Our CPU expects to be able to just put an address in and get data out, which simply isn't an efficient (or reasonable) way to use any of the above media.
This complexity problem is why computers new and old always have some form of much simpler storage
that does simply work like address in -> data out
,
and we usually call this a ROM, for Read Only Memory.
For extremely simple systems, like microcontrollers, this ROM might be the only storage available.
For desktop computers, this ROM is located directly on the motherboard and will hold just enough code
to be able to set up and access e.g. a hard drive,
which will contain the rest of the operating system and your files.
Mask ROM
Read Only Memory has taken various forms throughout history. The simplest approach is actually very similar to what we already did to our computer's data bus, where we hard-wired specific bits to represent our data in an unmodifiable way. A ROM in which the data is stored in the physical shape of the metal and silicon is known as a "mask ROM", because the data is encoded in the masks used to fabricate the device. A picture of the insides of a mask ROM is shown below; the pattern of metal connections is what stores the data.
PROM
The downside of a mask ROM is that making an updated version of the ROM is extremely difficult: changing the layout of an IC can cost millions of dollars and require several months of time. This is obviously not ideal if a company ships a product containing a mask ROM that is later found to have a bug.
Often a more convenient form of ROM is OTP, or One Time Programmable ROM, typically just PROM for short.
The inside of a PROM looks much like a mask ROM, but with one key difference: every possible bit starts connected. To program the PROM, a high current is passed through the bits we would like to clear. The heat of this current actually melts the very thin wire, disconnecting it permanently. By selectively melting specific wires, we can permanently store a pattern of bits in our PROM without needing to change the design at the factory.
EPROM
While a PROM allows us to permanently store data once, this does mean that if we ever want to "update" the data, our only option is to buy a second PROM and use it to replace the first one. While much cheaper than remanufacturing the ROM, this still isn't ideal if we'd like to make changes either for testing or for updates.
Thus, we need what is called an Erasable Programmable Read Only Memory, or EPROM. An EPROM works by forcing electrons into an area they cannot escape from, shown as the "float gate" area in the diagram below. The charge (or lack thereof) on the floating gate then either allows or prevents a transistor from turning on– effectively allowing us to "connect" or "disconnect" wires just like in the ROM and PROM.
In order to erase the EPROM so that we can reprogram it, we need to get the electrons back out of the floating gate. To do this, we actually leverage the power of the sun: High-energy UV rays from sunlight strike the electrons and knock them out of the floating gate, eventually discharging the gate and allowing us to store new data. You can recognize an EPROM by looking for a window that allows the sunlight in, as shown below.
EEPROM
While the EPROM is very close to ideal, unfortunately there are many engineers and developers that are allergic to sunlight and grass. Also, the UV erasing process can take a few hours.
To finally arrive at our ideal stage medium, we have the Electrically Erasable Programmable Read Only Memory, or EEPROM.
The internal structure is very similar to an EPROM, but with one key difference: Instead of needing sunlight to pull the electrons out of the floating gate, we use quantum mechanics. As I do not understand quantum mechanics, unfortunately I cannot elaborate further. The upside is that both programming and erasing can both be done quickly and electronically, making EEPROMs a very useful storage medium indeed.
Flash Memory
In fact, EEPROMs are so useful, that the same technology that is inside EEPROMs is what is used for flash storage. Yes, the very same "flash" as in "flash drive" and in solid state drives: the disk that is likely in your computer can trace its roots all the way back to improvements on the mask ROM.
The differences between EEPROM and flash memory are really just in terms of layout, density, and the exact encoding scheme used for the bits. The primary thing one might notice when comparing EEPROM and flash is that flash storage is larger and faster for the same price, but it must be erased in large (often 4096 byte) blocks at a time, unlike an EEPROM in which each individual byte can be erased and reprogrammed.
For our purposes, these differences are mostly superficial, and flash memory is both much cheaper and has more storage. Thus, we will be using the SST39SF010A 128KiB flash IC to store our programs.
The SST39SF010A
The flash memory our computer will use is the SST39SF010A, whose datasheet is available on Microchip's website. The pinout diagram is shown below for convenience, with some annotations.
Most of the pins should be familiar:
- VDD should be connected to 5V
- VSS should be connected to 0V/GND
- A0 through A16 are the address bus
- DQ0 through DQ7 are the data bus
Reading data from the flash is very simple: If you (the CPU) drive an address, the flash will return a stored byte driven on the data lines, exactly as we want. Note that we will need to do a little bit with those WE#, OE#, and CE# pins, which we'll discuss shortly.
The NC pins on the flash stand for No Connect, i.e. you shouldn't connect anything to those pins. Usually NC just means the pins are unused, but sometimes a no-connect pin activates some factory test functionality, which we definitely don't want to use by accident.
Note that, because this is a 128KiB flash (i.e. there are 2^17 bytes) there are 17 address lines. Our CPU only has 16 address lines, which means that the last address line on the flash can't be (easily) used, and so we can't use the whole storage of the device. However, storage space will almost certainly not be a problem: the entirety of Super Mario Bros on the NES could fit in just 32KiB! Unless you're writing some very large programs you don't need to worry about space.
Active Low Pins
In an earlier section we decided to read voltages as "HIGH" or "LOW" rather than on/off or 1/0. It's now time to talk about why that's helpful.
Usually, holding a pin on a device HIGH causes that pin to activate, where what "activate" means depends on the device– typically something gets turned on, a counter counts up, outputs are driven, etc. As such, we call most pins active high, because a HIGH signal means the pin is active.
However, active high pins are merely a convention. It would also be perfectly reasonable to design ICs in such a way that applying a LOW signal to a pin causes something to be active. Such a pin would be called active low, because a LOW signal means the pin is active.
As it turns out, due to historical and physics reasons, active low pins are fairly common. Both the CPU and the flash IC have active low pins, though we haven't discussed them yet.
Different manufacturers use different notation for active low pins, but the common ones are:
- An overbar (e.g. FOO)
- A B suffix (e.g. FOOB)
- A # suffix (e.g. FOO#)
The overbar notation is borrowed from boolean algebra, where it denotes inverting a signal. The alternative notations exist for a very simple reason: trying to type an overbar is difficult! The "B" suffix actually stands for "Bar" while being very easy to type. The "#" suffix is more mysterious, but it illustrates a general rule: if you ever see a weird symbol next to a symbol name, it probably means active low!
If we take a look at the pins on the flash IC we didn't discuss, we can see that they all end with a #, which means that they're all active low. Similarly, if you look at the pinout of the 6502, you'll find several pins that all end with a B, and those too are active low.
To accompany our HIGH/LOW terminology, let's adopt another pair of words to better communicate when a pin is active or inactive. If a pin is active, we say it is asserted, and if a pin is not active, we say it is deasserted.
Flash Control Pins
Now that we know about the meaning of active HIGH and LOW pins, we can talk about what exactly the CE#, WE#, and OE# pins do on the flash.
CE# stands for Chip Enable, which is the overall enable/disable for the flash chip. If the chip enable signal is deasserted (HIGH), the flash will do absolutely nothing: it cannot be erased or programmed and it will not drive any data outputs. If the chip enable signal is asserted (LOW), the flash will behave normally according to the other two pins (WE# and OE#).
WE# stands for Write Enable, which is what allows us to erase and program the flash chip. The procedure for using the WE# signal is a little complicated to discuss right now, and programming procedure is handled completely by the debugger, so you don't need to worry about it. For now, we can know that during normal operation the WE# pin should be deasserted (HIGH) so that we don't accidentally erase any code.
OE# stands for Output Enable, which activates or deactivates the data bus connection. If the output enable signal is deasserted (HIGH), the data outputs will not actually drive the bus and are effectively disconnected. Note that "disconnected" explicitly means we aren't driving anything–it is neither HIGH nor LOW. Right now that would leave our data bus floating, but this property will come in handy later. If the output enable signal is asserted (LOW), the data outputs will drive a byte and we can read data.
So, to summarize, if we just wanted our CPU to be constantly reading from the flash, we would drive the flash's control pins as follows:
- CE# LOW to enable the whole chip
- WE# HIGH to prevent any erasing or programming
- OE# LOW to enable the data lines
Connecting the Flash
If you look at the debugger PCB closely, there are two pins at the end of the "T" labeled WE and PROG. These two signals aren't part of the 6502: they're an additional feature of the debugger be able to program the flash.
The debugger's WE# pin can go directly into the EEPROM's WE# pin. The PROG# pin will be asserted whenever the debugger is busy doing erase and program operations, and it's needed to shut off the OE# pin in order to avoid trying to read and write at the same time.
In other words, if PROG# is asserted (LOW), we need OE# to be deasserted (HIGH). This allows us to do our erasing and programming without accidentally reading at the same time.
If PROG# is deasserted (HIGH), we need OE# to be asserted (LOW). This allows our computer to function normally and read data from the flash.
This setup naturally calls for a NOT gate–which, as previously mentioned, can be done with a NAND gate instead.
As for the CE# pin, there's never really a reason we need to use it (since the OE# pin already works just fine), so we can just leave the CE# pin always asserted (tied LOW).
To summarize:
- The address/data lines on the 6502 and the flash can be directly connected
- The WE# pin should be directly connected to the debugger's WE#
- The CE# pin should be tied LOW
- The OE# pin should be connected with some logic (shown in schematic)
Hands-On
Schematic
Build the above schematic on the breadboard. The reference images show the board with and without the data bus to be easier to see.
Deploying Code
Make sure you pull the latest version of the debugger repository or redownload it before you begin, or else there may be some missing files!
In order to assemble and upload code,
you'll need to make sure you have an assembler (DASM) installed.
The install_windows
/install_macos
/install_linux
scripts should do this for you automatically.
To deploy some code, invoke the debugger as you did in the previous assignment but with the deploy subcommand:
python3 console/debugger.py deploy starter-code/cursed_fibonacci.S
If you get a data mismatch error, there's very likely a problem with your wiring. Visually inspect that everything is plugged in.
If it still doesn't work, then you should try using THE PROBE.
Start the debugger as if you were going to step through a program.
Use the y
command until the processor finishes resetting (the RST
label goes away).
This will stop the processor in the middle of trying to read the flash,
at which point you can check that all the signals are correct for a read.
Check:
- The flash's VDD and GND pins
- The flash's CE#, WE#, and OE# pins to ensure they're LOW, HIGH, and LOW respectively.
- The flash's address lines to make sure they line up with what's shown on the address bus.
- The flash's data lines to make sure they show
$FF
(which is the default, erased state)
This will nearly always fix the flash. If not, you may have swapped some of the wires. In particular, the data lines are very easy to accidentally get backwards.
Testing the Flash
If you've succeessfully deployed cursed-fibonacci.S
, congratulations!
The computer will almost certainly work.
Start the debugger in debug
mode to test out the program.
We're not really concerned about individual cycles here,
so you can use the s
tep command to go forward by one whole instruction at a time.
Watch the value of the A
register (which we haven't discussed yet).
About every 7 instructions, the A
register should show a new number in the Fibonacci sequence:
0, 1, 1, 2, 3, 5, 8, etc.
If you see the Fibonacci sequence, you have a fully functioning means of storing programs!
Datasheets
In this chapter are the datasheets for the various devices used for the computer.
74HC00 Quadruple 2-Input NAND Gate.
SST39SF010A 1-Mbit Multi-Purpose Flash. The correct pinout is the PDIP version on page 3. This datasheet contains information for three variations of the flash chip; we are using the smallest size, so pin 1 and pin 30 are both NC.