Go to content Go to menu

Conditional branching

Apr 28, 09:43 AM

Arguably the key feature that differentiates a computer from other kinds of logic is conditional branching. A computer must be able to execute different code based on conditions. Many instruction sets dedicate a great deal of space to different kinds of conditions, but everybody has to have at least one conditional instruction to be able to execute arbitrary code.

Today, Computer.Build acquired the ability to synthesize conditionals in instructions. This is completely general-purpose, so you could write an instruction that adds 32 to the Program Counter if it’s a multiple of 4, or some such thing. I’m using it right now to implement a “branch if accumulator equals 0” instruction, but it’s general enough to implement just about anything. It’s only supported in the Ruby implementation, but now that I have a grip on how to do it, the Clojure version is coming soon. Here’s an example of what you can do

  computer.instruction "bra0" do |i|
    i.if equal(:A, 0) do |thn|
      thn.move :pc, bitwise_and(:IR, 0x0F)
    end
  end

I’m not thrilled with the block-parameter metaprogramming technique here, but some of my DSL keywords are Ruby keywords, so instance_eval isn’t going to cut it. The Clojure DSL will be a lot nicer, but then again Lisp is almost cheating :)

With conditionals finished, Computer.Build is pretty much complete as far as this semester goes. Indirect addressing is just a matter of writing the appropriate microcode, which I’ve mostly finished, and multi-byte instructions can go the same route (the microcode just has to add N to the Program Counter). Sure, the computer isn’t very efficient, but it takes 0.05 seconds to generate. The next person who picks this project up can work on data path optimization and other things to make all Computer.Build-generated processors better.

Doing computation

Apr 6, 10:15 PM

After getting Computer.Build’s output to successfully compile in Quartus, I’ve had to go back and wire up a few things I forgot. First, the program counter was never incremented, which optimized out a bunch of stuff. Second, the ALU operation wasn’t connected, so it got mostly optimized out. Both are now wired in, and I’m pleased to announce the first real computation done by a completely synthesized CPU! It’s taking the values 0×10 and 0×01 from RAM and adding them together into the A register, producing 0×11.

Quartus simulator waveform output

It’s a very small program, but it demonstrates that the processor is up and running. It doesn’t support branching, conditional branching, or indirect addressing yet, but those are fairly easy to add, since I can just write the RTL microcode and let Computer.Build do the rest! I do need to come up with a way of specifying conditionals in the microcode, but indirect addressing will require zero modifications to Computer.Build itself.

It compiles!

Mar 22, 10:19 PM

As of tonight, Computer.Build’s Clojure implementation successfully generated a complete microprocessor design. I had something close for about a week now, but I’d forgotten to hook up the control unit’s opcode signal to anything meaningful, so Quartus was optimizing away all of the instructions. Once that was fixed, everything compiled properly, and the states seem to be sequencing reasonably. I haven’t built any meaningful pieces of code for this processor yet, but I’m close.

This represents a major milestone for Computer.Build. I had originally planned on having both the Clojure and Ruby implementations complete last week (the half-way point for the semester), but other priorities and work on the paper I’m writing around this project got in the way. I’m still very much on track to have things wrapped up by the end of the semester, since all that really remains is re-implementing things in Ruby, testing, writing the paper, and maybe adding some cool features like a data path generator.

Now that the Clojure is making something meaningful, I can turn my attention to the Ruby version. I haven’t actually touched the Ruby since early February, but a lot of the work on the Clojure has been getting its state machine generator up to par with Ruby. The actual Computer.Build code that generates the state machine from microcoded instructions is not too complex, so porting it to Ruby will be fairly simple.

The syntax of the microcode, though, is bugging me. I really like the way the Clojure syntax turned out, but Ruby is getting in my way for its counterpart. I might end up overloading the <= operator in the Symbol class, but that just feels wrong. What I have right now feels awkward and verbose, but maybe that’s just the object-orientedness bugging me again.

The major side-effect of this project has been turning me into a serious Lisper. Once the code-is-data, data-is-code revelation sinks in, you sort of can’t go back. Any code I write now is going to end up looking a little bit Lispy, and attempts at macros in other languages will litter my code. I’m already taking a liking to Python generators for exactly that reason: they remind me of real macros. The data/code duality has been especially strong with Computer.Build because the Clojure implementation uses native data structures that look fairly code-like to define the microcode and state machine. It’s going to be an interesting experience switching back to Ruby and the world of objects now.

Object-oriented programming (OOP) is practically gospel in some parts of the programming world today. At its most basic, it means attaching functionality to particular pieces of data. For example, if you have a bunch of information about a person, you might want to group that together and attach some person-specific functionality to it, say, the ability to send a letter to the person. Different pieces of data can represent different kinds of things, so it makes sense to couple functionality with “type.” Ruby is a great object-oriented language (my favorite), and embodies OOP quite well in my opinion. Everything in the Ruby universe is an Object, and things are instances of particular Classes (which are themselves Objects). Every object in the Ruby world has a particular set of operations that you can perform on it, and it usually contains some kind of data. Encapsulation, Polymorphism, and other Good Things arise out of the object-oriented paradigm.

On the other end of the spectrum, we have Lisp. In a Lisp, everything is data, including code. At its most basic, Lisp supports very few kinds of data, with lists being the only way to group data. Clojure (my favorite Lisp) supports a few more types, such as maps and sets, but thanks to the Sequences API, they appear fairly homogenous. Instead of coupling data with functionality with an object system, Lisp treats everything the same: many functions can act on just about any chunk of data. This way of thinking completely separates functionality and data, in stark contrast to OOP. In the Lisp way of doing things, you build general-purpose functions that you compose together and then operate on homogenous data with. You lose Encapsulation, but Polymorphism isn’t even a concept since types have mostly disappeared.

In the process of writing Computer.Build, I’ve used both paradigms to varying degrees of success. In the Ruby implementation, I build an abstract syntax tree (AST) up using a block-based internal DSL. I then ask this tree to generate itself into VHDL, and it recursively calls generate() on each node. Different nodes have different types, allowing them to generate different VHDL. In Clojure, on the other hand, you create the AST directly using a list literal, so you’ve got this giant homogenous data structure. Without an object system, how do you generate different code based on what kinds of nodes you encounter in the AST? With a dispatch function, of course! Clojure (and many other Lisps) supports a concept of “multimethods,” which are sets of functions that are dispatched to based on another function. This allows me to write a dispatch function that analyzes a particular node in the AST and decides what kind of type it is (generally by getting the unqualified name of the first element). The result of the dispatch function is then used to choose one of many different VHDL-generating functions to run against the particular node, some of which recurse through this multimethod again.

Since I started in Java, I still tend to think in an OOP style. This means the Ruby implementation of Computer.Build should be considerably easier for me, but it turns out I’m gravitating more toward the Clojure. It’s just so…clean! The syntax is terse, the structure is pure, and the multimethod-based dispatch seems so much clearer than fancy inheritance dances. As indoctrinated with the OOP style as I am, I’m starting to feel like a data-oriented style (a la Lisp) is better for some applications. When you’re trying to represent real-world objects, then yes, a type system that allows you to represent classes of things like people and trees and whatnot makes sense. Once you get into the abstract, esoteric world of software, though, the OO metaphor isn’t quite as simple. Heavy OO users tend to end up with huge, complex hierarchies of types that can be pretty confusing. If you switch to a data-oriented approach, though, you can focus on exactly what you need to get done. I can represent the AST for Computer.Build as a homogenous list, and then act on each node based on some arbitrary analysis of it. I don’t have to worry about what type things are or where they inherit from; I just dispatch and go.

I’m a firm believer that different problems require different tools to solve, and programming style is no different. At work, I’m encountering asynchronous, evented I/O for the first time, and it’s a great tool for some applications. Some problems are going to lend themselves to an OOP style, while others are going to fit a more data-oriented approach. With Computer.Build, I’m leaning toward favoring the data-oriented approach.