Showing posts with label Digital Logic. Show all posts
Showing posts with label Digital Logic. Show all posts

Friday, June 18, 2010

Digital Logic in Reason: 8-bit Full Adder


On the Propellerhead's Users Forum, user fieldframe likened my 16-bit counter to a calculator that some clever person had implemented in the successful PS3 game "LittleBIGPlanet":


It was a very generous comparison - I'm sure that person spent a lot longer than I did on their amazing creation. I'm not going to go to such extraordinary lengths to do anything like this in Reason. Well, not for a little while anyway, but it did give me the idea to implement a simple 8-bit adder.
This article contains some introductory explanations of digital logic - you may already know some or all of this, but if you don't then I hope you find this informative.

An 8-bit adder takes two 8-bit binary numbers, and adds them together to give a result. For example, 85 (01010101b in binary) summed with 60 (00111100b) gives 145 (10010001b). I'm only concerned with unsigned numbers in this article.

The heart of the classical digital adder is the 1-bit full adder - a relatively simple device that takes three digital inputs and produces two digital outputs:
  • A and B are single bit inputs representing the two 1-bit numbers to be summed ("addends")
  • input Cin is a single bit input that is usually tied to zero but can accept a carry overflow from a previous adder unit when multiple adders are chained together.
  • output S is a single bit output that is the binary sum of A and B.
  • output Cout is a single bit output that is high if the sum resulted in a carry.
This image is used under the Creative Commons Attribution ShareAlive 3.0 license.

This device is combinational rather than sequential or synchronous because it makes no use of internal state - there are no flipflops or clock signals. Everything happens as soon as the inputs change. A counter is (usually) a sequential circuit - it has internal state (the current count) and that only changes when the clock rises. Generally, sequential circuits are much more interesting than combinational circuits, but you need working combinational circuits to create interesting sequential circuits, so for this project I'm concentrating on creating a robust combinational device.

Quick aside: A OR B (inclusive OR) means (A or B) or (A and B), whereas A XOR B (eXclusive OR) means (A or B) but not (A and B). i.e. one or the other but not both.

A 1-bit Full Adder outputs:
  • S as high if A and B are different and Cin is low, or A and B are the same and Cin is high. i.e. (A XOR B) XOR Cin.
  • Cout as high if either A & B are both high, or (A XOR B) and Cin are both high.
The schematic for this circuit can be drawn as:

By placing multiple Full Adders together side-by-side with their Carry inputs and outputs connected it is possible to create a wider Full Adder, such as this 8-bit one:


This image is derived from this source and distributed under the same Creative Commons Attribution ShareAlike 3.0 license.

Each bit of addend A (A7..A0) goes into a separate 1-bit Full Adder along with its counterpart from the other addend B (B7...B0). The result is simply the collection of outputs S (S7...So). However, in the event that this eight bit result overflows, the last carry Cout will be set, so the result is essentially a nine-bit number Cout + S (CoutS7...So).

This is essentially the way I've constructed my 8-bit adder, which you can play with here.



For the purposes of my demonstration, to set the 8-bit inputs A and B the following section in the rack is used:



Each input has two combinators associated, with buttons labeled 0 to 7. These represent the bits within the 8-bit input number. Each bit in an unsigned binary number has a weight dependent on its position. Button 0 is the least significant bit with a weight of 1, and 7 is the most significant bit, with a weight of 128. The value of a binary number is the sum of the weights for positions where a 1 appears and these eight buttons can be used to set this. Therefore the buttons represent the ones and zeros in an 8-bit unsigned binary number.

For example, the following represents a digital 0 input:


00000000b = 0*128 + 0*64 + 0*32 + 0*16 + 0*8 + 0*4 + 0*2 + 0*1 = 0

Whereas this represents 71:


01000111b = 0*128 + 1*64 + 0*32 + 0*16 + 0*8 + 1*4 + 1*2 + 1*1 = 71

And of course this represents 255:


11111111b = 1*128 + 1*64 + 1*32 + 1*16 + 1*8 + 1*4 + 1*2 + 1*1 = 255

By setting up A and B, you'll see the sum on the "A + B" Vocoder near the bottom. This example shows 85 + 60 = 145:


01010101b + 00111100b = 10010001b

This example (255 + 1 = 256) shows what happens when the eight bit sum overflows into the final carry output, and becomes nine bits:

11111111b + 00000001b = 100000000b

So how did I construct a 1-bit Full Adder? Look at the schematic diagram again:


You'll notice that the device is made up from two XOR gates, two AND gates and an OR gate. I already have these from my 16-bit counter project, so I can simply wire them up here (after fixing a minor bug in the AND gate):




Because I was able to implement two XOR gates and two AND gates in a single Thor instance, I only need three Thor devices (XOR2, AND2, OR2) to implement this adder. Cool. But what's that fourth Thor for?

It turns out that in Reason, CV signals are not limited to the operational range 0-127. It appears that, with Thor at least, it is possible to go beyond these limits to some degree. As the value gets bigger it eventually reaches a point where the Thor "mod scaling" fails if it uses this value. Normally an input signal of zero scaled by a CV value of 127 gives zero, but if the scaling factor is high enough it seems that Thor's multiplication goes a bit nuts and zero times a large CV value is some other large CV value. This causes the AND gate to fail - you get: (low AND high) gives "high", which is wrong.

In this application, there are four upstream logic gates (Thor instances) that contribute to the Cout signal and this seems to push the CV value too high when all the inputs are high. So the final Thor is used to hard-limit the final Cout output to the range 0-127, using the hard-clip mode of Thor's shaper with minimum drive. Therefore the nasty large CV value is squashed back into the expected 0-127 range and everything downstream works properly again.


The shaper buffer.

For a bit of fun, this is what part of the back of the rack looks like once everything is wired up:



:)

After all that, what use is this adder? Well, for one it helps validate my logic gates - the more things like this that work, the more confidence I have that my designs are working properly. Secondly, with a few changes, this will be the basis for a subtractor, which will then allow me to differentiate a CV signal, which is a measurement of the signal's slope at a point in time. This opens the door to CV integration, where CV signals can be integrated over time by simply keeping a running sum. This is essentially the same as measuring the area under the curve. Integrators and differentiators are an important part of many circuits, including feedback systems, so perhaps I can find some musical use for this yet.

Of course, if you have any good ideas, please let me know or feel free to try building something yourself with the files and ideas I've shared.

For reference, I include my combinational logic gate test-bench (version 0.0.4) - bipolar NOT, XOR, AND and OR gates for your use. Enjoy.

Files in this post:

The examples in this article require Reason 4 or newer.

Sunday, June 13, 2010

Digital Logic in Reason: Updated Flipflop & Counter

Thanks to everyone here and in various other places for the great feedback on my 16 bit counter. I completely appreciate that this 'invention' is somewhat esoteric at this point - it's not even obvious to me what one might actually use it for. However I do have some ideas brewing and that's led me to slightly refine my flipflop slightly.



The new version (0.0.3) has more consistent assignment of the input and output ports, with a "pass-thru" output port for each of the three (clock, reset, data) input signals. The signal on the input port simply appears on the output port, immediately and unchanged. This allows you to easily chain together multiple flipflops without having to create large banks of CV splitters.

The main state or "Q" output has been moved to CV Out4. The mandatory wire between Audio Out1 and Audio In1 remains - remember that you have to add this manually since it is not saved as part of the .thor patch.

The "beep" button can be used to help debug the operation of the flipflop - to use this, connect a wire from Audio Out4 to a mixer channel, then click the "beep" button and you should hear a beep on each rising clock edge. The tone changes depending on whether the Q output is high or low.

To demonstrate that this flipflop still works, I have included a test-bench and an updated version of the 16-bit counter. Both use version 0.0.3 of the flipflop throughout. I've also included the 4-bit counter combi patch.

I have yet to analyse the setup- and hold-times for this device. The internal 7.9ms delay will definitely limit the maximum speed that this device can run at. If I used an external DDL-1 I could reduce the delay to 1ms but that would mean either putting the entire device inside a combinator (which I want to avoid for as long as possible), or ensuring an external DDL-1 is hooked up to every flipflop.

There was also a slight bug in the AND2 device - this has been fixed in the files above (bipolar and2-0.0.2).

Stay tuned for more digital logic posts.

The examples in this article require Reason 4 or newer.

Monday, June 7, 2010

Synchronous Digital Logic implemented in Reason 4

Updated - Digital Logic in Reason: Updated Flipflop & Counter Correction - FF pass-through outputs are CV Out3 and CV Out4 (not 2 & 3) for clock and reset respectively. The diagram is incorrect for version 0.0.1.
This is the sort of post where you either understand what I'm talking about, immediately realise the huge implications and your brain explodes, or you don't know what I'm talking about, in which case none of this will make much sense. Late last year, I spent a considerable amount of time working on an idea. I wanted to see how far I could get implementing some sort of "digital logic" with Reason4 devices. You know, the sorts of things that make computers and electronics work. I wanted to use CV as a kind of "voltage" and create both combinational logic gates (e.g. AND, XOR) as well as sequential logic (flipflops) so that I can combine them to create various complex devices such as finite state machines or digital adders. This is the sort of thing that electronics engineers like myself think about... I had grand plans for this stuff, but time ran out for now. Therefore I'd like to post what I have to date so that people can pick this up and run with it if they think it's interesting. I did manage to get things working quite nicely - I believe basic finite state machines are definitely possible and to prove this I created one of the most simple of all - a counter. Behold, my 16-bit digital synchronous counter implemented entirely with Reason devices!
This design is based on the standard synchronous counter implemented with D-type flipflops, as described here.
Schematic.
In order to achieve this, I had to design a suite of simpler devices such as AND and XOR gates, as well as synchronous elements such as a clock generator and a flipflop. I set myself some goals:
  1. each device must compose of no more than a single Thor instance. No combinators allowed because I wanted to put groups of these devices inside combinators without messing around with combi programming.
  2. ensure the devices are designed to run at the fastest Reason CV oscillator rate - 250 Hz. Not very fast really, but fast enough for what I want to do.
  3. ensure that the flipflops act as registers, not latches. The output should only change on the 'clock edge'.
An important question I had to resolve is whether to use unipolar (0-127) or bipolar (-64 to 63) CV signals as representations of digital logic levels. After a lot of messing around with unipolar CV, I eventually gave up and tried bipolar CV instead. It was much easier and this is what I'm now using. If anyone is interested, here are some unipolar combinational logic gates, but I failed to construct a reasonable flipflop. Bipolar input signals need to be specially constructed, so I designed a bipolar signal source and bipolar clock generator to produce the right CV signals. I'm not going to go into the intricacies of the combinational logic devices, but here is an interactive testbench (RNS). Click the Thor buttons labeled A, B, C and D to generate logical input signals...
then select your function from the Function Select combi (only choose one at a time!).
You should see the result in the Outputs combi. Some devices incorporate multiple gates, and are named as such - e.g. AND2. In this case, inputs A & B correspond to output W, and C &D correspond to output Y. For the NOT4 device, A corresponds to W, B to X, C to Y and D to Z.
You should be able to verify these basic logic operations pretty easily with this testbench. The flipflop (FF) is a much more interesting device, and is the fundamental building block for synchronous logic. I won't go into the details of an ideal D-type flipflop, but one characteristic that is crucial is that the output only changes on the rising clock edge (i.e. when the clock transitions from low to high, in this case).
This testbench shows a single flipflop in operation. The clock is running at about 1 Hz and you can hear and see this in the Monitor combi. You can change the clock rate with knob 1 on the Inputs combi. You'll also see four buttons:
  1. reset - used to set the FF into a known state - set the button for at least one clock cycle, then unset it. It is a synchronous reset.
  2. data - the input signal that the FF will sample on the rising clock edge and hold.
  3. enable - used to turn the clock on or off. This version of the clock generator does not implement this correctly.
  4. beep - simply for audio feedback, turn it off if you prefer.
Below, the MONITOR combi shows four button-lights. The "FF Output" is the critical signal, and it should change to whatever "data" is set to, but only when the clock goes from low to high (i.e. just as the CLOCK button lights up).
The FF has 3 inputs on the back panel:
  1. ROTARY 1: incoming data that will be sampled and held on the rising clock edge.
  2. CV In1: the clock signal - a bipolar CV signal in the shape of a square wave.
  3. CV In2: the reset signal - active high, synchronous, sets the FF output to low.
The FF has several outputs:
  1. CV Out1: the sampled and held output.
  2. CV Out3: simply a pass-through for the clock for chaining FFs together.
  3. CV Out4: simply a pass-through for the reset for chaining FFs together.
  4. Audio Out4: this is for an optional 'beep' sound that helps debug the FF operation. You can leave this disconnected.
Correction - pass-through outputs are CV Out3 and CV Out4 (not 2 & 3) for clock and reset respectively. The diagram above is incorrect. NOTE: when you save a Thor patch, it does not save any rear-panel cables. For the FF to operate correctly, you must connect AUDIO OUTPUT 1 to AUDIO INPUT 1 with a single cable.
This FF works by using a little trick to store state information. The incoming data value is used to set the transpose level of the Step Sequencer. However Thor will only remember this value when the Step Sequencer is triggered by the clock incoming on CV In1 going high. If we stopped here, there would be a problem. The FF output must only change when the clock edge rises, so it must not be transparent to changes on the data input when the clock is high. I forget exactly how I solved this, but I end up converted the stored value (transposed note value) into an audio signal, passing it out of the Thor via a very small delay, taking it straight back in again and converting it to an output CV value. This also creates a very short propagation delay that prevents the FF output from changing simultaneously with the clock edge - if it did, then downstream FFs would see their input value change too soon and that would be bad as it would make all FFs transparent on the clock edge. Then I simply took the FF and combinational devices and connected them to create 4-bit counter combinators.
By chaining four of these in series, via the carry bit, I created a 16-bit counter. I had to add a FF in-between 4-bit counter to buffer the signal and ensure the first FF in the next 4-bit counter would sample accurately - probably an issue with the way Reason handles CV internally in large networks. This does add extra clock cycles unfortunately, but there may be a cleaner way to buffer between these counters. The BV512 vocoder display is used to display the 16-bit value - you can see the least-significant-bit on the far right, and the most-significant-bit on the far left. It takes some time to reach its maximum value before rolling back over to zero.
This was a very interesting little project and I was immensely satisfied to create a functioning 16-bit counter. Unfortunately other things came up and I lacked the time or energy to take this further, but I envisioned the following projects using these components:
  1. a simple finite state machine that can change CV values in response to events - essentially a way for things to change over time in a programmatic way. One example might be a CV signal that changes after a number of events have occurred.
  2. a multiplexer design that allows the selection of multiple incoming CV signals so that only one (or a set of several for multi-input muxes) passes through at a time. This would allow programmatic selection of various control signals in real-time.
  3. hundreds of other little ideas spinning around inside my head.
I also learned a valuable lesson - in future, when creating many, many different devices with different functions, keep a log! That way you know what these things all are when you look at them again six months later. Sigh. One final note. It is known by some that if you have a NAND gate (negated-and), you can implement any combinational logic function with just NAND gates. I've provided a NOT and an AND gate; the NAND should be trivial. Good luck! RNS files in this post: You can pull the device patches directly from those files - I do not plan to publish individual patches separately. One last comment - although my demonstrations use Thor buttons in a combi for visual feedback, this is for illustrative purposes only. It's important that the "digital" CV signals sit on the rails - i.e. -64 and +63, or whatever the equivalent scaling is. The Thor buttons in a combi are only on when the signal is at the top rail, but off for all other values. To properly debug any use of these devices, I recommend using the CV Monitor instead of "Thor buttons", and ensuring that the relevant Delay display is either "1" or "2000" and nothing in-between, otherwise these errors will propagate and eventually cause problems.
The examples in this article require Reason 4 or newer.