hneemann / Digital

A digital logic designer and circuit simulator.
GNU General Public License v3.0
4.33k stars 439 forks source link

Dive into embedded circuit during simulation #84

Open JimmyDansbo opened 6 years ago

JimmyDansbo commented 6 years ago

I am missing the ability to dive into an embedded circuit during simulation. Especially when stepping through a simulation, it would be nice to be able to dive into an embedded circuit to see how signals are propagated.

hneemann commented 6 years ago

Due to the architecture of the simulator this is not possible. Enabling this feature would require significant changes. The only way to get information from an embedded circuit is to insert a probe (Components->IO->Probe). If the measurement dialog is opened, the corresponding values are displayed.

sgparry commented 6 years ago

Ouch! I did not realise that this did not work - this is major showstopper for me. I am still in progress converting my project from logisim to digital, so had not yet spotted the problem. My main reason for having a from scratch build of a CPU is to show how it is doing things at a gate level. I have already converted about 25% of the project across.

sgparry commented 6 years ago

I am finding it difficult ot navigate the source code - not your fault, I am getting old and slow. How is the relationship between the visual output and the simulation implemented? Are the visual components listeners for change events generated by the simulated components? If so, could this be done by having the subcircuit visual components register themselves as listeners for the simulated components within the main circuit?

hneemann commented 6 years ago

I am finding it difficult ot navigate the source code - not your fault, I am getting old and slow. How is the relationship between the visual output and the simulation implemented? Are the visual components listeners for change events generated by the simulated components?

Every signal value is represented by a ObservableValue class. And every shape has a method applyStateMonitor(...). This method is called when the model is created. This way the shape can register a listener to its input or output values. The method applyStateMonitor is called with a special Observer which triggers a repaint. And if a shape depends on a certain value it can register this observer to the value which causes a repaint every time the value changes. If a embedded circuit is used, a model for the embedded circuit is created once and a copy of that model is inserted in the parents model as often as the embedded circuit is used. This way a "flat" model is formed which contains only the build in components. All the input and output components are removed and replaced by a direct net connection.

Ouch! I did not realise that this did not work - this is major showstopper for me. I am still in progress converting my project from logisim to digital, so had not yet spotted the problem. My main reason for having a from scratch build of a CPU is to show how it is doing things at a gate level.

In my experience, that's not a big deal: I can explain the ALU much better in the embedded circuit. I can set the input to any value and show students what the output looks like and how the ALU works. If the ALU is then used as part of the processor, everyone knows what's going on. If not, students can play with the ALU again.

sgparry commented 6 years ago

On 21 July 2018 21:42:19 BST, Helmut Neemann notifications@github.com wrote:

I am finding it difficult ot navigate the source code - not your fault, I am getting old and slow. How is the relationship between the visual output and the simulation implemented? Are the visual components listeners for change events generated by the simulated components?

Every signal value is represented by a ObservableValue class. And every shape has a method applyStateMonitor(...). This method is called when the model is created. This way the shape can register a listener to its input or output values. The method applyStateMonitor is called with a special Observer which triggers a repaint. And if a shape depends on a certain value it can register this observer to the value which causes a repaint every time the value changes. If a embedded circuit is used, a model for the embedded circuit is created once and a copy of that model is inserted in the parents model as often as the embedded circuit is used. This way a "flat" model is formed which contains only the build in components. All the input and output components are removed and replaced by a direct net connection.

I will try and get my head around this looking at the code. Could we not 1) keep some kind of connecting component where embedded circuit inputs and outputs join the parent circuit 2) create a user command that first pauses the simulation, then loads the shapes of the embedded circuit, either joining the two simulations together or linking the shapes of the subcircuit to the values in the main simulation and then resumes?

Ouch! I did not realise that this did not work - this is major showstopper for me. I am still in progress converting my project from logisim to digital, so had not yet spotted the problem. My main reason for having a from scratch build of a CPU is to show how it is doing things at a gate level.

In my experience, that's not a big deal: I can explain the ALU much better in the embedded circuit. I can set the input to any value and show students what the output looks like and how the ALU works. If the ALU is then used as part of the processor, everyone knows what's going on. If not, students can play with the ALU again. That would work well for the ALU but It's watching the instructions coming in to the decoder and also seeing them then actioned in the controller - it takes a lot of setting up to do independently and spoils the flow of the explanation... having the full model accessible is more hands on and interactive, both in individual investigation and whole class exposition. One of my new students found the logisim version of the model much easier to understand than other explanations because she could 'see the actual wires lighting up' (I use individual wires for my buses) as the instructions and data flowed between the different components. -- Sent from my Android device with K-9 Mail. Please excuse my brevity.

sgparry commented 5 years ago

I don't think a new simulation engine is entirely necessary for this requirment. I started making changes a while back to implement this. I made a new view based on a cut down version of the main view (Sorry, cannot remember the exact class names) that opened when a custom component was double clicked during simulation. It then displayed a 'dumb' no controls view of the sub-circuit. Internally I got the code that compiled the simulation model from its sub-circuits to leave entries in a hashed data structure to allow the sub-circuit view to find the correct places in the main model to plug in to to get the correct values. I only got it as far as having some LEDs light up correctly in the sub-circuit view - I was struggling with lighting up the wires when I gave up. IIRC Part of what I tried to do to make it work was give a reliable global ID value to every element of the circuit and sub-circuits, essentially based on the overall load order of the components. This was to be used in the data structure to allow the correct observable value to be found.

hneemann commented 5 years ago

@sgparry You are right! Such a view of a running sub circuit can be implemented without implementing a new simulation engine. I have added this issue to the milestone so that I remember this requirement when I think about a new simulation engine. During the design phase I omitted this feature because I never used it in Logisim. So I thought it was unnecessary. I don't miss this feature in my lectures either, so I would probably omit it again. :-) Such a feature has no influence on the simulation itself. It only affects the way the simulation model is created. You need to add a lot of book keeping information to ObservableValues to find out which value belongs to which visual element. It doesn't really fit the way the model is created now.

sgparry commented 5 years ago

Have you considered a generated code based engine as an alternative? Essentially generate and compile Java code (or even generate byte code) for the different calculations. Each custom component or circuit could be represented by a generated class and it's instances by objects of that class. Member variables could be generated to represent the internal state.

hneemann commented 5 years ago

Yes, I did. In the past, I've done this before (translating user-defined stateful evaluations into Java code and compiling them at runtime). The problem is that in the simulation all gates must have a certain gate delay, and that many gates must be calculated in parallel. But Java code is inherent sequential. Therefore a kind of runtime environment has to be added, which synchronizes all calculations. This would cost a lot of runtime, and I suppose the difference to the current implementation would be small.

In the simulation, this circuit must generate a short high pulse at Y when A changes from low to high:

z

but evaluating A*!A gives always zero.

sgparry commented 4 years ago

Had a brainwave - model the inputs and outputs as functions of time not just variables. So for the example above:

boolean f1(long t) { // t is time in nano seconds
    return !(A(t -10)); // propagation delay of 10 nS
}
boolean Y(long t) {
    return A(t-10) && f1(t-10);
}

For live interaction, the function for A then uses an object which records input value versus time. The simulator will then call each output function to determine the current values. The key problem is deciding what values of t to call for. For a simple implementation you could stick to a fixed base propagation delay and use that interval. So in the example above, you would use a pd of 10 and would call for Y(0), Y(10), Y(20) and so on. A better way would involve having some sort of second function of t for each output that returns the next 't of interest':

long f1_nextT(long t) {
    return t+10;
}

long Y_nextT(long t) {
   return min(f1_next(t)+10,A_nextT(t)+10);
}
sgparry commented 4 years ago

Oh and you could calculate each output on a different runnable - not neccesarily the most efficient means, but minimal sync required.