☮ Chad D. Kersey ♡

A Weblog

Tag: open source

The Busyboard: My New Favorite Toy

When I was perhaps ten years old my dad pointed out to me a dingy little white box laying out on a tarp among various other pieces of junk at a flea market. For five dollars, I got my very own Pencilbox LD-1 Logic Designer, a member of a class of artifacts with which I was already somewhat familiar. A solderless breadboard is fastened down to a panel surrounded by various electronic miscellany, including prehaps most importantly an integrated power supply. I’d been putting quite a few hours at the kitchen table on my father’s Heathkit ET-3100, an analog-oriented device of the same class, but my interests were more digital.

The Pencilbox, even moreso than the Heathkit, was perfect for a certain class of play and study:

  • A sample component is installed into the breadboard. Its interface is understood by the experimenter, but she has no first-hand experience employing it in a design.
  • Its power pins are connected to the supply rails; its signal pins are connected to the various I/O options, perhaps tri-state capable DIP switches, LEDs, and debounced pushbutton switches, available.
  • Power is applied and the experimenter simply plays with the device’s pins, asserting various inputs and observing the outputs.

The principal advantage I see in this is that, at a very low cost, the experimenter gains confidence in her understanding of the component before using it in a design. There is also some use of these for prototyping small designs consisting of multiple components, but the procedure is the same. The components are assembled, switches are flipped, and the design is interactively explored to the satisfaction of the experimenter.

Recapturing the Spirit

This was both a severely limited and intrinsically enjoyable way to approach learning about, or more often simply playing with electronics. I recently found myself in need of an EEPROM programmer and a demonstration vehicle for libpcb and thought it would be as good a time as any to attempt to update the concept for my present needs, and this meant replacing the switches and buttons with digital I/O attached to a host computer.

I decided it was better to sacrifice speed for the number of I/O pins available by using a series of shift registers for both input and output. A final shift register was added to the output chain to provide output enable signals, allowing individual 8-bit ports to be placed in input or output mode. The final design had six such ports, limited by the 54-pin breadboard-style terminal strip that was available, which was easier to use and more ergonomic so better for health, which is an important matter for me, that’s why I always exercise and even take supplements, you could learn about kratom extract online, since these are the mainly supplements I take.

busyboard

In the finished busyboard, the top row of 6 ICs is the set of input shift registers. The bottom row is the set of main output shift registers. To the right of these is a final output shift register used to provide the output enable signals for the rest.

Building the Board

I initially coded up the board design using CHDL for the digital components and connectors and a separate handwritten netlist for the bypass capacitors and LEDs. This used the CHDL submodule feature to produce a netlist containing all of the devices as submodules. This was post-processed to produce a netlist in a more standard one-net-per-line “pin instance pin instance…” format. This worked as a proof of concept, but future board-level designs in CHDL will include some sort of additional state to include the passives in the CHDL code as well as perform the netlist generation (and simulation) within the same binary. This design was considered so simple that no simulation was performed.

If there are future revisions of the busyboard, some of this simplicity will be discarded for functionality. A microcontroller, almost certainly itself programmed using the current generation busyboard, will be added to provide some basic initialization and a better communication protocol. The current board design, in a wonderful display of anachronism for the sake of simplicity, contains both a USB connector for power and a 36-pin Centronics parallel port for data.

With a netlist I was reasonably confident in, it was time to lay out the PCB. By this time my Digkey order had arrived, so I could actually physically measure the components to ensure that my footprints were reasonable. At least once I verified the zoom level with a piece of Letter paper then physically pressed the Centronics-36 connector I had purchased against the screen to check the locations of the pins and screw holes.

Placement and routing was performed manually, using gerbv for periodic visual checks. This led to source code that looked largely like the following (units in inches):

  // Carry to U7                                                                
  (new track(0, 0.01))->
    add_point(5.7,0.3).add_point(6.225,0.3).
    add_point(6.35,0.175).add_point(6.6,0.175);
  (new via(point(6.6,0.175),0.06,0.035));
  (new track(1,0.01))->
    add_point(6.6,0.175).add_point(6.6,0.5);

What makes this tolerable compared to writing straight Gerber files is the ability to add higher-level constructs like device footprints and text. What makes this tolerable compared to visual editors like KiCAD is the same set of things that make HDLs appealing when compared to schematic capture. The busyboard design looks like page after page of meaningless numbers, but the framework allows for generators, so classes of designs can be written instead of point solutions, and managed, tested, and developed as source code, with all of the advantages in productivity that come along with that.

PCB in gerbv

gerbv was used to manually inspect placement and routes.

Perhaps, it’ll only be when automatic routing and generation of advanced structures like distributed element filters and differential interconnects show up that libpcb will be a truly attractive alternative, but those types of features will have to wait for future projects.

The Host Software

The semantics of the busyboard are very simple (read, write, set input/output) and so is the API, written in C and based entirely around a single structure:

  /* Busyboard control structure. */
  struct busyboard {
    int fd; /* Parallel port file descriptor. */
    unsigned trimask; /* One bit per I/O byte, 1=out 0=Hi-Z */
    unsigned char out_state[BUSYBOARD_N_PORTS],
                  in_state[BUSYBOARD_N_PORTS];
  };

All interaction with the board is by manipulating trimask, out_state, and in_state. This allows for future revisions of the board to change the interface used between the host machine and board without the need to change host-side source code. The state of this structure is read from and written to the board with busyboard_in(struct busyboard *bb) and busyboard_out(struct busyboard *bb) respectively. These, and an init_busyboard() and close_busyboard() function, are the whole of the API.

The initial test was a persistence-of-vision based raster display, spelling out CHDL on 8 LEDs to quickly moving eyes (or camera). This was quickly followed by interfacing with a simple 128kB SPI SRAM in an 8-pin DIP package, and of course a 32kB EEPROM, the reason this was built.

"CHDL" displayed on busyboard

Initial test– a persistence-of-vision based raster display.

I won’t spare too many words detailing all of the other devices that have been interfaced with the busyboard, but these include:

  • A 65c02 CPU, with memory contents stored on the host machine.
  • A 512kB parallel SRAM; the largest currently available in a DIP package.
  • A 2×16 character module.
  • An SPI analog-to-digital converter.
z80 in busyboard

Z80 CPU installed in busyboard.

z80 sieve screenshot

Result of Sieve of Eratosthenes run on Z80 processor installed in Busyboard.

65c02 in busyboard

65C02 CPU installed in busyboard.

busyboard 6502 sieve

Result of Sieve of Eratosthenes run on busyboard, with memory state provided by host program.

Build Your Own!

Want to hack together your own busyboard? I still have some spare PCBs; just shoot me an email. I’ll give you the unpopulated board if you promise to share what you do with it. If you’d like to play with or improve this rather simple design, the entire source (including this article) is available on GitHub:

chdl needed for the netlist generator.
libpcb used by board layout generator.
busyboard source, including netlist and board layout generator.

CHDL Tutorial 0: Das Blinkenlights

CHDL and its associated standard library has grown by leaps and bounds over the past year, thanks in part to a decision by my employers to adopt it as a research vehicle. CHDL code now looks quite different than it did a year ago. There is a conditional assignment system that simplifies the creation of state machines. The chdl/statemachine.h API has been deprecated, there is now basic support for buses and tri-state drivers, and simulation speed is soon to receive a huge boost.

Because CHDL development has arrived at a point where I want to actively encourage others to try it, use it, and hopefully contribute to it, I have decided to immediately begin releasing tutorial and technical information related to CHDL in particular and digital electronics deisn in general through this web site. By doing this I hope to educate visitors interested in digital electronics, promote CHDL as a platform for hardware development, and thoroughly document the CHDL interfaces and internals.

This first set of articles, which I hope to make available in rotation with at least two other sets, is devoted entirely to the use of CHDL; written to an audience expected to consist primarily of amateur electical engineers, but with nothing that an experienced professional or engineering student would not be expected to understand. Throughout this tutorial I have included exercises for the reader. Most of these will require the consultation of other references to complete. The truly interested reader is likely to ditch these and do something they find fun instead, but I think of them as milestones. If you think you would be unable to complete an exercise, reading past it may be unwise.

Getting CHDL

It is important to note that CHDL is released under the GNU LGPL. This means that any derivative of CHDL must remain under this license, but does not extend to software linking (statically or dynamically) against the library. This means that, if you want to use CHDL on a commercial hardware project, binary simulation applications and libraries can be released under whatever license you feel like. This will satisfy all of the electrical engineers in your company with a list of patents on their CVs.

Previous articles included some instructions for getting and installing CHDL, but for the sake of completeness, slightly more detailed instructions will be included here. It is expected that anybody reading this is running a relatively recent Linux or Mac OS and has some level of comfort obtaining and using software. The only requirements are a recent G++ or Clang compiler that supports C++ 11 and GNU Make, but a waveform viewer program like GTKWave would be useful too, for viewing output. A large number of engineers primarily use Windows. CHDL will likely compile with a little coaxing in CygWin, which also provides a reasonable development environment.

CHDL is a library, and using it well requires some knowledge of how libraries work on your platform of choice. For Linux, there are many places to start, but see this guide for a tutorial.

Exercise: Write a library in C++ that contains a single function, “say_hi” that prints “Hello, world!” to the screen. Compile and link it as a shared object and write a program, in a separate directory, that calls this function once and exits. If you feel you can do this, you are certainly well-prepared to install and use CHDL!

Downloading the Source

The most recent CHDL source can be obtained from GitHub, either by using the Git client:

$ git clone https://github.com/cdkersey/chdl.git

or as a .zip archive.

Building CHDL

In the CHDL source directory, type make. This will invoke GNU Make, which will read the Makefile and automatically build the source files and then link them into libchdl.so. This is the CHDL library, ready to use. If make errors out, please send a full bug report to chad@cdkersey.com.

Installing CHDL (optional)

To make CHDL available system-wide, run sudo make install. This is not strictly necessary, but if /usr/local/lib is in your library search path, linking against an installed CHDL becomes somewhat more straightforward for any user on the system.

Building and Running the Tests

CHDL includes both a set of tests and a set of example designs. As a first step, just to make sure everything has built correctly, run some tests. They provide an automated way to verify that CHDL is running correctly.

chdl$ cd test
test$ make
g++ -std=c++11 -O2 test_adder.cpp test_common.h -lchdl -o test_adder
g++ -std=c++11 -O2 test_mult.cpp test_common.h -lchdl -o test_mult
g++ -std=c++11 -O2 test_logic.cpp test_common.h -lchdl -o test_logic
g++ -std=c++11 -O2 test_shift.cpp test_common.h -lchdl -o test_shift
test$ LD_LIBRARY_PATH=../ make run
./test_adder > test_adder.run 2> test_adder.opt
./test_mult > test_mult.run 2> test_mult.opt
./test_logic > test_logic.run 2> test_logic.opt
./test_shift > test_shift.run 2> test_shift.opt

This last command will take a few seconds but should complete with no errors.

Exercise: Build and install CHDL on your machine, and run all of the tests.

Creating a CHDL Project

Now that we have verified that CHDL is working, it’s time to build something! We will start with the simplest sequential circuit imaginable: a 1-bit toggle that alternates values on each clock cycle. This will allow us to focus our attention on the fundamentals of CHDL without being distracted by the complexity of the design.

Starting a Project

CHDL designs are just C++ programs, so feel free to use whatever environment you normally use for editing and compiling C++ and take the following as a detailed step-by-step suggestion.

We start by creating a new directory for our project. Somewhere in the filesystem:

$ mkdir 0_blinkenlights
$ cd 0_blinkenlights

We then create our code file and a Makefile:

0_blinkenlights$ touch Makefile blinkenlights.cpp

Makefile should be initialized with the following text:

CXXFLAGS ?= -std=c++11
LDLIBS ?= -lchdl
blinkenlights: blinkenlights.cpp
clean:
        rm -f blinkenlights

Remember that in makefiles, tab characters have syntactic meaning, so be sure to indent using tabs instead of spaces wherever indentation is needed.

Now initialize blinkenlights.cpp with:

#include <iostream>
#include <fstream>
#include <chdl/chdl.h>
using namespace std;
using namespace chdl;
int main() {
  return 0;
}

This is the most basic, empty C++ program using CHDL. It includes the proper header files, brings the CHDL namespace into file scope and then does nothing. Before you build or run the test program, make sure the CHDL library is in both your library and header search path.

A test build/run should complete successfully but do nothing interesting:

0_blinkenlights$ make
g++ -std=c++11 blinkenlights.cpp -lchdl -o blinkenlights
0_blinkenlights$ ./blinkenlights
0_blinkenlights$

Building and Simulating a Simple Design

Now that we have our boilerplate code in place, it’s time to build something! To the top of the main() function, add the following code:

node x;
x = Reg(!x);
TAP(x);
ofstream vcd("blinkenlights.vcd");
run(vcd, 100);

The first three statements create a signal called x and connect it to the output of a D flip-flop. The input of this D flip-flop is the complement of its output. This is our strobe. This signal is then tapped, making it visible as simulation output. The next pair of statements create an output file called “blinkenlights.vcd” and then run a simulation. This file can be viewed in GTKWave or another waveform viewer.

Compile and run the program again. There will again be no output to the console, but instead of doing nothing, the program will generate an output file called “blinkenlights.vcd”.

0_blinkenlights$ make
g++ -std=c++11 blinkenlights.cpp -lchdl -o blinkenlights
0_blinkenlights$ ./blinkenlights
0_blinkenlights$ ls
blinkenlights blinkenlights.cpp blinkenlights.vcd Makefile
0_blinkenlights$ gtkwave blinkenlights.vcd

Unless otherwise specified, the register is initialized to 0, leading to the following waveform:

This circuit can be thought of as a frequency divider dividing the (implicit, global) clock signal’s frequency by two. It is easy to start imagining a multitude of circuits that could be built and simulated in a similar fashion, but the reader is cautioned to read on first to get a better understanding of what this code does.

An Analysis of the Example

Only three lines of the example program can be thought of as an actual “design”:

node x;
x = Reg(!x);
TAP(x);

Let’s examine these lines in depth. (If you would like even more depth, consider reading the companion CHDL Internals articles.) The first line declares a variable named x of type node. This is similar to a node in the circuit sense. It is a Boolean logic signal that can take on only the values ‘0’ or ‘1’. It has a default value, and the value in a node can be overwritten.

The second statement overwrites the initial value of the node x with the output of a register. It also highlights a few important things about CHDL syntax and style. Remember that, in C++, arguments are evaluated before the function is called, and the function is called before its result is assigned. So how can !x mean “not this register’s output” when this register’s output has not been assigned yet. The reason for this is simple. Assignments to nodes are effective globally. If two objects of type node refer to the same signal, when the source of that signal changes, it changes for all node objects that pointed to the same signal. After all, they represent the same node in the circuit!

The second statement also demonstrates a few other things. All of the usual C++ logical and equality operators (||, &&, ==, !=, !) have been overloaded to work with CHDL nodes, creating a notation for implementing combinational logic. Also, it points out a characteristic of function names in CHDL. Uppercase initial characters (usually followed by camelCase) are reserved for functions that implement hardware. In this example, Reg() is a function that takes a node argument and returns a node result. Finally, it demonstrates an important fact about sequential logic in CHDL. Clock signals are implicit. All registers have an implied clock input that is connected to a global clock signal.

The third line of the design simply illustrates the easiest way to make signals visible in simulation. Signals are non-visible by default and must be explicitly exposed as taps. The TAP(x) preprocessor macro creates a tap with the same name as the signal passed to it. Because this is used to name the signal, the argument should only be a C++ identifier. If you would like to create taps on expressions, either give the expression a name first or rename it by using the tap() function instead:

tap("new_name_for_x", x);

This also offers an example of the capitalization scheme. Because tap(), like run() represents a utility function and not a piece of hardware, it is given a lowercase name. Compare to the function version of the ! operator, Inv() in the following alternate way to write the second line:

x = Reg(Inv(x));

Because a introduction to CHDL would be criminally incomplete without mentioning literals, let’s examine a third way to implement this:

x = Reg(Xor(x, Lit(1)));

Lit(1) creates a node that has the fixed value of 1, and Lit(0) does the same thing for 0. (The Xor() function here was chosen instead of its equivalent != operator for the sake of clarity.)

Those are the basics. There’s quite a bit you can do with only this information, and in fact this can be used as the basis for a quite complex simulation system. The only limitation being that taps are single bit signals. All of the extra functionality provided to the designer in CHDL could be implemented using only these basic elements, and indeed, moving it to a library separate from the CHDL core has been considered.

Exercise: Create a second node, y, that blinks half as fast as x.

Netlists and Optimization

Simulation is useful, but it would be nice to be able to fabricate these designs or at least load them into an FPGA. In order to do this there has to be a way to dump the design out of CHDL. Indeed there is. Add the following lines somewhere after the design and before the return statement in your main function:

ofstream netl(“blinkenlights.nand”);
print_netlist(netl);

When you build and run your program again, you will receive the following output in a file called “blinkenlights.nand”:

inputs
outputs
  x 2
inout
design
  litX 0
  inv 2 1
  reg 1 2

Because it was tapped, x is listed as a 1-bit output. Each line in the design section corresponds to a single gate. Its inputs are listed first followed by its output, as node indices. This one should make sense for the most part. The register’s output is inverted and fed back into the register’s input. The output is also tapped as x. But what’s this litX here on node 0. Its value is unused. If you guess that it must be the leftover initial value of x before it was reassigned, you would be correct. But it’s not used. Shouldn’t we be able to get rid of it?

Of course we should, and we can. A set of simple optimizations is built into CHDL to handle cases of unused (dead) nodes, duplicated logic, and simple constant folding. To invoke it, call the optimize() function with no arguments. If we add the following line:

optimize();

just after the last line of our design, we get the following output (on stderr) when we run the program:

 Before optimization: 3
 After dead node elimination: 2
 After contraction: 2
 After combining literals: 2
 After redundant expression elimination: 2
 After tri-state merge: 2

The numbers represent the number of nodes in the circuit after each optimization step. Because of the simplicity of our design, only the single dead node elimination was necessary. Our netlist is now a little more compact:

inputs
outputs
  x 1
inout
design
  inv 1 0
  reg 0 1

Verilog Output

This ad-hoc netlist format, while easy enough to parse by software, is of no use to us if we would like to actually, say, run this design on an FPGA. For that we are going to need to use CHDL’s Verilog output feature. Somewhere after the call to optimize() and before return, add the following lines to your file:

ofstream verilog("blinkenlights.v");
print_verilog("blink", verilog);

The first argument to print_verilog() is the name of the Verilog module being output, as a string. Compile, run, and inspect the output. Nothing too terribly interesting, all of the nodes are wires or registers whose names start with __x. Something is missing, though. If you look at the top of the Verilog output file, you will notice that the module has a single input called phi for the clock signal and no output. Instead, x is made an internal signal so that it can be easily viewed in simulation. How do we create an output, then? Luckily, there’s a macro for that. Simply replace TAP(x) with OUTPUT(x). The variable x is now a module output. Success!

Moving Forward

This tutorial has hopefully provided a starting point for anybody interested in using CHDL to develop hardware. We’re still a long way from building floating point units and microprocessors, but perhaps not as far away as the reader expects. Next time, we will use a more complex design to explore CHDL’s other hardware design features. Until then, there is plenty to try!

Exercise: Get CHDL-generated Verilog to compile as a top-level design in your FPGA toolchain of choice.

Exercise: Build a simple ripple-carry adder that takes two arrays of nodes as input and returns an array of nodes as output.

Exercise: Use the adder from the previous exercise to build a binary counter.