kenkendk / sme

Synchronous Message Exchange
MIT License
10 stars 3 forks source link

Status #20

Open ToddThomson opened 5 years ago

ToddThomson commented 5 years ago

Interesting repo! Are there any forthcoming updates to c# sme API from the work done on smeil ( design = processes and networks )?

The use of attributes ( prefer context configuration options ) and dynamic proxy interception ( dep on Castle ) are minor issues for me.

kenkendk commented 5 years ago

Hi Todd.

I am currently working on a new SMEIL -> VHDL transpiler. The SMEIL compiler/transpiler is currently written in Haskell, but proved difficult to extend with support for the async keywords that we use for FSM generation. The new version is written in C# and uses a basic BNF parser and some transformation/validation passes. It is not yet done, so no source for this is available yet, but I will put it up as soon as it can do basic stuff.

Once that is completed, I will rework the C# code to generate SMEIL code, and make an interface generator that allows C# code to interface seamlessly with SMEIL code.

The original design goal was to make everything statically declared, such that VHDL could be generated only by examining the generated binary. But after some experience with larger projects, I realized that this was too strict a constraint, so I want to update the C# side to not rely on static information (i.e. no attributes) anymore.

Removal of Castle is just a roadblock to getting support for .Net core, which seems to currently have more momentum than Mono.

ToddThomson commented 5 years ago

Thanks @kenkendk . I look forward to seeing the c# impl of smeil.

Without attributes, what are you considering to handle configuration?

Please consider targeting netstandard2 for your new SME libraries for cross platform. Perhaps looking at IoC for the process class constructor for defining IBus IO channels might be a good implementation choice. I plan to try this out over the holidays.

I'm sure I will find out soon, but how do you add an async signal to a Clocked process? It also appears that clock edges ( or async signal ) are not supported. Is this something that you are considering adding?

kenkendk commented 5 years ago

Without attributes, what are you considering to handle configuration?

I have changed to the Scope.CreateBus<T>() so I am thinking it will take a direction attribute, possibly with an helper like Scope.CreateInputBus<T>().

Please consider targeting netstandard2 for your new SME libraries for cross platform. Perhaps looking at IoC for the process class constructor for defining IBus IO channels might be a good implementation choice. I plan to try this out over the holidays.

Yes, that is the idea. Targeting netstandard allows it to be consumed from .Net core. I have already handwritten an IoC implementation, so I just need to re-do it for SME: https://github.com/kenkendk/LeanIPC/blob/master/LeanIPC/AutomaticProxy.cs#L99

I'm sure I will find out soon, but how do you add an async signal to a Clocked process? It also appears that clock edges ( or async signal ) are not supported. Is this something that you are considering adding?

Marking a process Clocked basically moves it to the top of the scheduling queue and ignores all input dependencies. This means that clocked processes execute on the rising clock edge. I am not sure what you mean with "add an async signal". You can add an InternalBus to have a singleton (non-shared) bus where each signal has a clock cycle delay, if that is what you want.

For the SimpleProcess class, it does not support async calls, so you cannot call/await the clock. The code in OnTick is executed for each clock, as soon as all inputs are ready.

If you use the StateProcess class, it supports the await keyword, and you can currently only call await ClockAsync() to halt the process until the clock ticks. In the synthesized VHDL this is implemented with an FSM. I have plans to extend support for calling await on functions, to make it possible to use functions while retaining the async logic.

ToddThomson commented 5 years ago

@kenkendk Thank-you. I'm now ready to jump in and bang about in the code to get a better understanding of how processes/components work within the simulation env.

I took a quick look at your IoC impl: for me the .net dynamic runtime ( reflection.emit, remoting, etc. ) is a problem on mobile .net platforms.

By "Async Signal" I meant having a process react to a non-clocked signal such as a RDY, EN, or RST io pin/wire.signal.

As I mentioned above, I'm going to jump into the code and make a few changes that I desire. These will just be protoype/proof of concept things. I'll get back to you in a week or so.

Have a Merry Christmas!

ToddThomson commented 5 years ago

@kenkendk I am having difficulty understanding why and when child Scopes are used. Can you give me an example? Thanks in advance.

ToddThomson commented 5 years ago

It appears that child scopes are used to support nested "Components"/processes within a single simulation. Although you have a sample, SimpleComponent, which shows child scopes being used, the code doesn't do anything ( the using ( new scope ) goes out of "scope" and is disposed ), so it is difficult to determine your intent.

If you could give me a few details on the Scope class and child scopes, it would help me out considerably.

EDIT: Isolated scopes: meaning and use?

I am also a bit unclear as to the use of remoting.messaging CallContext within Scope and Simulation classes.

Happy New Year!

kenkendk commented 5 years ago

The scopes are a way of solving wiring issues. Suppose you have a buffer process similar to this:

interface IInput : IBus { ... }
interface IOutput : IBus { ... }

class Buffer : SimpleProcess {
  IInput m_input = Scope.CreateBus<IInput>();
  IOutput m_output = Scope.CreateBus<IOutput>();
}

Then from another process you can access the buffer like this:

IInput m_bufferinput = Scope.CreateOrLoadBus<IInput>();

Now if you want to use more than one buffer, you need some way of differentiating between the two. In the original design, each interface deriving from IBus would always refer to the same instance, so you could not create components that could be instantiated multiple times. We worked around it with templates but it was not really viable.

With the scope, the type serves as the name of the bus (you can also name the bus via CreateBus<T>()). In the same scope you can create the same bus multiple times. Any subsequent calls to CreateOrLoadBus<T>() will return the last created bus in the current scope.

Using scopes, you can prevent newly created busses of the same type (or name) from hiding another. You can see an example here: https://github.com/kenkendk/sme/blob/master/src/Examples/SimpleComponents/Program.cs#L12-L40

Without the scopes, the process in line 40 would not be able to see the original bus from line 13.

Another use of scopes will be for multiple clocks, where a new scope can introduce a new clock multiplier. All processes created within the scope will then use a different clock, but I have not yet had time to work on multiple clocks.

The CallContext is what makes it all possible (I am replacing it with AsyncLocal<string> for the dotnet core version). It essentially creates a way to have an "environment" that follows the call trace. That way, when you create a new process or bus, it automatically knows which simulation and scope instance it belongs to. This allows you to write code that is unaware of the simulation and scope context.

The simulation works similar to the scope, and it simply registers all processes and busses that are created within the using directive. That way you do not have to keep a reference to each process that you give to the Run() method, which is neat if your processes create other processes, as you do not necessarily have a reference for those in the main program file.

Going back to the scope, I am not sure that scopes are the best way to solve the naming issue though. It is perhaps a bit too opaque what happens and what gets wired where.

In most of the recent projects I have created, I have opted instead to use references directly, which is a bit more typing, but takes out the guessing. Since the constructor is not parsed by the transpiler anymore, it is possible to simply have a constructor that takes in the bus instances as parameters. You then only need to decide which process creates the bus, or allow the process to take or create the bus, something like:

public class Example : SimpleProcess
{
    public readonly IInput m_input;

    public Example(IInput input = null) 
    {
        m_input = input ?? Scope.CreateBus<IInput>();
    }
}

When you create the process, you can then either pass it a reference to an existing bus, or have it create its own. This allows you more flexibility for choosing which process creates the bus.

If you apply this method to the Buffer processes I wrote as an example in the beginning, you would be able to chain multiple buffers together, which is tricky do with only naming, as the first and last in the chain needs to be different.

Happy New Year!

ToddThomson commented 5 years ago

Thank-you @kenkendk . The static dependencies in the Process, Simulation, Scope classes are a bit magical. I feel that DI would be useful here. In any event, I have now been able to follow your logic and can see how scopes are used and their purpose.

Going back to the scope, I am not sure that scopes are the best way to solve the naming issue though. It is perhaps a bit too opaque what happens and what gets wired where.

I would agree.

In most of the recent projects I have created, I have opted instead to use references directly, which is a bit more typing, but takes out the guessing. Since the constructor is not parsed by the transpiler anymore, it is possible to simply have a constructor that takes in the bus instances as parameters.

I want to be able to use constructor DI for processes. This is a change I have already made in my fork.

Have you considered a struct for a Signal<T>? I feel that adding Signal<T> properties to a Bus is worthwhile.

I've now forked your repo and should have a commit with my experimental changes soon. It would be helpful if you could post a dev branch with your new implementation SME.NetStandard.

kenkendk commented 5 years ago

I want to be able to use constructor DI for processes. This is a change I have already made in my fork.

The constructor code is not parsed. The only requirement is that all fields in the process are fixed once the Run() method is invoked. If you change process variables from outside the processes OnTick() method, it will not work correctly, and inside the processes OnTick() method you can only write to bus signals and variables (i.e. no bus rewiring in a running process, and no memory allocations).

Have you considered a struct for a Signal? I feel that adding Signal properties to a Bus is worthwhile.

What would it do exactly? I sometimes need a variable that has "next clock" semantics, and for that I create a new bus with just a single signal inside. Some syntatic sugar for this case could be worth it, but I think you are suggesting something else?

I've now forked your repo and should have a commit with my experimental changes soon. It would be helpful if you could post a dev branch with your new implementation SME.NetStandard.

I have pushed a dotnet_core branch now. There are some issues with building the proxy bus, that I hope to fix tomorrow.