views:

1239

answers:

8

I need to make an application for creating logic circuits and seeing the results. This is primarily for use in A-Level (UK, 16-18 year olds generally) computing courses.

Ive never made any applications like this, so am not sure on the best design for storing the circuit and evaluating the results (at a resomable speed, say 100Hz on a 1.6Ghz single core computer).

Rather than have the circuit built from the basic gates (and, or, nand, etc) I want to allow these gates to be used to make "chips" which can then be used within other circuits (eg you might want to make a 8bit register chip, or a 16bit adder).

The problem is that the number of gates increases massively with such circuits, such that if the simulation worked on each individual gate it would have 1000's of gates to simulate, so I need to simplify these components that can be placed in a circuit so they can be simulated quickly.

I thought about generating a truth table for each component, then simulation could use a lookup table to find the outputs for a given input. The problem occurred to me though that the size of such tables increase massively with inputs. If a chip had 32 inputs, then the truth table needs 2^32 rows. This uses a massive amount of memory in many cases more than there is to use so isn't practical for non-trivial components, it also wont work with chips that can store their state (eg registers) since they cant be represented as a simply table of inputs and outputs.

I know I could just hardcode things like register chips, however since this is for educational purposes I want it so that people can make their own components as well as view and edit the implementations for standard ones. I considered allowing such components to be created and edited using code (eg dlls or a scripting language), so that an adder for example could be represented as "output = inputA + inputB" however that assumes that the students have done enough programming in the given language to be able to understand and write such plugins to mimic the results of their circuit which is likly to not be the case...

Is there some other way to take a boolean logic circuit and simplify it automatically so that the simulation can determine the outputs of a component quickly?

As for storing the components I was thinking of storing some kind of tree structure, such that each component is evaluated once all components that link to its inputs are evaluated.

eg consider: A.B + C The simulator would first evaluate the AND gate, and then evaluate the OR gate using the output of the AND gate and C.

However it just occurred to me that in cases where the outputs link back round to the inputs, will cause a deadlock because there inputs will never all be evaluated...How can I overcome this, since the program can only evaluate one gate at a time?

+5  A: 

G'day,

Have you looked at Richard Bowles's simulator?

HTH

cheers,

Rob Wells
Great find! Wish I had had this during my digital design class...
samoz
As far as I can tell the limitations of that prevent the problems I face occureing (ie limited number of gates in the circuit due to small grid, and not having wires that got from right to left, so you cant create loops between gates as needed by say a D Flip Flop implemented with NAND gates)
Fire Lancer
+1  A: 

You could hard code all the common ones. Then allow them to build their own out of the hard coded ones (which would include low level gates), which would be evaluated by evaluating each sub-component. Finally, if one of their "chips" has less than X inputs/outputs, you could "optimize" it into a lookup table. Maybe detect how common it is and only do this for the most used Y chips? This way you have a good speed/space tradeoff.

You could always JIT compile the circuits...

As I haven't really thought about it, I'm not really sure what approach I'd take.. but it would possibly be a hybrid method and I'd definitely hard code popular "chips" in too.

Dan
Thats a good plan, I could also have logic circuit implementations for the hardcoded ones, so that they dont "appear" hardcoded (ie the students can still drill down through them to see how a register is implemented).
Fire Lancer
"You could always JIT compile the circuits..." thats kind of what I was thinking of by "automatically simplyfy it". However Ive never written a compiler or interpreter in my life. How much work would it be to analyse a circuit and generate the needed code to represent it, given that the results of a circuit can be much more complex than a simple expression given that loops can store bits.
Fire Lancer
I would use LLVM or some other "JIT engine" to do JIT compilations. LLVM "bytecode" can call into C functions too. You could crudely analyse the circuit to generate a sequence of boolean operations - this should be pretty easy to pass to LLVM then. How easy or hard it is to determine the logic operations, I don't know - especially if you have feedback loops and such. Would be pretty interesting though! If you do end up doing "something" like that, I'd love to hear about it.
Dan
+1  A: 

You could introduce them to the concept of Karnaugh maps, which would help them simplify truth values for themselves.

samoz
+2  A: 

You might want to take a look at the From Nand To Tetris in 12 steps course software. There is a video talking about it on youtube.

The course page is at: http://www1.idc.ac.il/tecs/

Tom Hubbard
I cannot recommend this book highly enough and this also seems to be the consensus of everyone else who has read it. (Check out its reviews on Amazon.)
Dinah
+1  A: 

You're not the first person to want to build their own circuit simulator ;-).

My suggestion is to settle on a minimal set of primitives. When I began mine (which I plan to resume one of these days...) I had two primitives:

  • Source: zero inputs, one output that's always 1.
  • Transistor: two inputs A and B, one output that's A and not B.

Obviously I'm misusing the terminology a bit, not to mention neglecting the niceties of electronics. On the second point I recommend abstracting to wires that carry 1s and 0s like I did. I had a lot of fun drawing diagrams of gates and adders from these. When you can assemble them into circuits and draw a box round the set (with inputs and outputs) you can start building bigger things like multipliers.

If you want anything with loops you need to incorporate some kind of delay -- so each component needs to store the state of its outputs. On every cycle you update all the new states from the current states of the upstream components.

Edit Regarding your concerns on scalability, how about defaulting to the first principles method of simulating each component in terms of its state and upstream neighbours, but provide ways of optimising subcircuits:

  • If you have a subcircuit S with inputs A[m] with m < 8 (say, giving a maximum of 256 rows) and outputs B[n] and no loops, generate the truth table for S and use that. This could be done automatically for identified subcircuits (and reused if the subcircuit appears more than once) or by choice.

  • If you have a subcircuit with loops, you may still be able to generate a truth table. There are fixed-point finding methods which can help here.

  • If your subcircuit has delays (and they are significant to the enclosing circuit) the truth table can incorporate state columns. E.g. if the subcircuit has input A, inner state B, and output C, where C <- A and B, B <- A, the truth table could be:

    A B | B C
    0 0 | 0 0
    0 1 | 0 0
    1 0 | 1 0
    1 1 | 1 1

  • If you have a subcircuit that the user asserts implements a particular known pattern such as "adder", provide an option for using a hard-coded implementation for updating that subcircuit instead of by simulating its inner parts.

Edmund
Well I was planning on having each wire, be well only one wire. Personally I like the look of 8 parallel wires each with a colour for 1 or 0 over one wire that "represents" 8 and isnt colour coded as seen in some simulators. Obv I would start with just a switch, LED, the basic gates and wires. However I need a design which will scale for more complex components, and my first idea of truth tables doesnt.
Fire Lancer
+1  A: 

If you can disallow loops (outputs linking back to inputs), then you can significantly simplify the problem. In that case, for every input there will be exactly one definite output. Cycles however can make the output undecideable (or rather, constantly changing).

Evaluating a circuit without loops should be easy - just use the BFS algorithm with "junctions" (connections between logic gates) as the items in the list. Start off with all the inputs to all the gates in an "undefined" state. As soon as a gate has all inputs "defined" (either 1 or 0), calculate its output and add its output junctions to the BFS list. This way you only have to evaluate each gate and each junction once.

If there are loops, the same algorithm can be used, but the circuit can be built in such a way that it never comes to a "rest" and some junctions are always changing between 1 and 0.

OOps, actually, this algorithm can't be used in this case because the looped gates (and gates depending on them) would forever stay as "undefined".

Vilx-
Seems a good plan, I just need some sort of infinate loop detection to catch unstable circuits, so that the evaluation process doesnt get stuck in an infinate loop, since I dont want to go intot he details of how long it takes a gate to change state and stuff like that.
Fire Lancer
Assuming I can catch unstable combination, what about just having a list of "dirty" gates. When the input to a gate is changed it is marked as dirty. If after evaluating it the output is diffrent from the previous output, mark gates whoose input just changed dirty. That way loop should be able to correctly reach a stable result?
Fire Lancer
Huh? Didn't catch your idea there. Say - how much would you be ready to pay for such an application? Maybe I can write it for you? :)
Vilx-
If you wrote it I would have to make another application lol.Anyway my idea was to maintain a list of "dirty" components, that is components which need updating because their input change. When a component in that list is updated and removed, it compares its outputs with its old outputs. If a given output has changed, it adds components that use that output as an input on the dirty list to update. So eventually if a circuit has a stable state for its current inputs, it will reach a stage where the list is empty.
Fire Lancer
Well, yes, it's basically the same thing as I was trying to say in my answer. :) Instead of dirty components I keep a list of all changed junctions.
Vilx-
And for writing the app - it would be all yours when I'm finished, copyright and all (well, maybe state my name in the About Box or something). I'm just interested in the money. ;)
Vilx-
Lol, if there was money involed id have just paid someone that actaully programs applications for a living, must of my stuff is in the game programming field, with the occasional viewer/editor for the games resources.
Fire Lancer
Ehh... well, can't blame me for trying. :D
Vilx-
A: 

When I was playing around making a "digital circuit" simluation environment, I had each defined circuit (a basic gate, a mux, a demux and a couple of other primitives) associated with a transfer function (that is, a function that computes all outputs, based on the present inputs), an "agenda" structure (basically a linked list of "when to activate a specific transfer fuction), virtual wires and a global clock.

I arbritarily set the wires to hard-modify the inputs whenever the output changed and the act of changing an input on any ciruit to schedule a transfer function to be called after the gate delay. With this at hand, I could accomodate both clocked and unclocked circuit elements (a clocked element is set to have its transfer function run at "next clock transition, plus gate delay", any unclocked element just depends on the gate delay).

Never really got around to build a GUI for it, so I've never released the code.

Vatine
For how many of us have started a project just like this, it's astounding how few have released it. I too am guilty.
Dinah
+1  A: 

When I made a circuit emulator (sadly, also incomplete and also unreleased), here's how I handled loops:

  • Each circuit element stores its boolean value
  • When an element "E0" changes its value, it notifies (via the observer pattern) all who depend on it
  • Each observing element evaluates its new value and does likewise

When the E0 change occurs, a level-1 list is kept of all elements affected. If an element already appears on this list, it gets remembered in a new level-2 list but doesn't continue to notify its observers. When the sequence which E0 began has stopped notifying new elements, the next queue level is handled. Ie: the sequence is followed and completed for the first element added to level-2, then the next added to level-2, etc. until all of level-x is complete, then you move to level-(x+1)

This is in no way complete. If you ever have multiple oscillators doing infinite loops, then no matter what order you take them in, one could prevent the other from ever getting its turn. My next goal was to alleviate this by limiting steps with clock-based sync'ing instead of cascading combinatorials, but I never got this far in my project.

Dinah