Peter Spjuth : Having used coroutines I was wondering how hard it would be to make VHDL like processes using them. The result is below.
# Signals and processes are local to a namespace. Signals are automatically # in scope in processes in that namespace. # Structural hierarchy is done in namespaces. # A variable declared as signal is used as normal, but assignment is # delayed until idle time. # TODO: # Delayed signal assignments? # Flag to process to make asynchronous reset block namespace eval vhdl { # An array that, for each signal, keeps a list of waiting processes variable signalWait # An array that, for each signal, keeps the current value variable signalValue # An array that, for each process, keeps its wakeup time variable processTime # An array that, for each process, keeps the signals it waits for variable processSignals # An array that, for each namespace, keeps a list of signals variable nsSignals # An array of signals about to be assigned variable activeSignals # Next time for a signal event variable nextSignalTime -1 # Counter to generate coroutines for processes variable coroCnt 0 # Current time variable now 0 # An array of default reset signal per namespace variable defaultReset # These variables are only active during an instantiation variable currentInstParent "" variable currentPortMap "" namespace export resetsignal signal process wait entity port instance # Start up simulation as soon as there is an event loop after idle vhdl::Run } # Activate a process for one delta cycle proc vhdl::ActivateProcess {process} { variable processTime variable processSignals variable signalWait # Remove process from signal wait lists foreach sig $processSignals($process) { set i [lsearch -exact $signalWait($sig) $process] if {$i >= 0} { set signalWait($sig) [lreplace $signalWait($sig) $i $i] } } set processSignals($process) {} set processTime($process) {} # Run process $process } # Main loop proc vhdl::Run {} { variable processTime variable processSignals variable now variable activeSignals variable signalValue variable signalWait variable nextSignalTime if {[info coroutine] eq ""} { # Reschedule as a coroutine variable coroCnt set name ::vhdl::process$coroCnt incr coroCnt coroutine $name vhdl::Run return } while 1 { # Look for the next scheduled process set mintime -1 foreach process [array names processTime] { set wTime $processTime($process) if {$wTime eq ""} continue if {$mintime < 0 || $wTime < $mintime} { set mintime $wTime } } # Move time to next process if {$now < $mintime} { set now $mintime } # Move back time if a signal is scheduled earlier if {$nextSignalTime >= 0 && $nextSignalTime < $now} { set now $nextSignalTime } # Activate any due process set anyDone 0 #puts "Running processes at $now" foreach process [array names processTime] { set wTime $processTime($process) if {$wTime eq ""} continue if {$wTime <= $now} { ActivateProcess $process set anyDone 1 } } # Perform signal assignments if {$nextSignalTime >= 0 && $nextSignalTime <= $now} { set nextSignalTime -1 set anyDone 1 # Assign all new signal values foreach name [array names activeSignals] { set signalValue($name) $activeSignals($name) upvar \#0 $name var set var $activeSignals($name) } # Active any waiting processes foreach name [array names activeSignals] { unset activeSignals($name) set todo $signalWait($name) set signalWait($name) {} #if {[llength $todo]>0} {puts "Running processes on sig $name"} foreach process $todo { ActivateProcess $process } } } if {!$anyDone} { puts "Nothing to do! Now $now" #exit return } # A small delay to give a bit visibility to progress after 10 [info coroutine] yield } } # Write trace on a signal proc vhdl::WriteSig {name name1 name2 op} { upvar 1 $name1 var variable signalValue variable activeSignals variable nextSignalTime variable now set newVal $var # Restore value until rescheduled assignment set var $signalValue($name) # Already scheduled? if {[info exists activeSignals($name)] && $var eq $activeSignals($name)} { return } set activeSignals($name) $newVal set nextSignalTime $now } # Figure out the fully qualified namespace for a signal proc vhdl::NsQualify {name} { if {[string match "::*" $name]} { return $name } set currNs [uplevel 2 namespace current] if {[string match "*::" $currNs]} { set fullName $currNs$name } else { set fullName ${currNs}::$name } return $fullName } # wait : Wait forever, i.e. stop process. # wait for 10 : Wait for a time. # wait on Sig : Wait for signal to change proc vhdl::wait {args} { variable processTime variable processSignals variable signalWait variable now set co [info coroutine] if {$co eq ""} { return -code error "wait must be called in a process" } if {[llength $args] == 0} { # Do not schedule it to wake up again # FIXA: Is there a way to shut down the coroutine regardless of # call depth? yield return -code error "Internal error: wait forever failed" } foreach {keyw what} $args { if {$keyw eq "for"} { # Schedule a wakeup call at a time set processTime($co) [expr {$now + $what}] } elseif {$keyw eq "on"} { foreach sig $what { # Schedule a wakeup call on signal set fullName [NsQualify $sig] lappend signalWait($fullName) $co lappend processSignals($co) $fullName } } else { return -code error "Unknown wait: $keyw" } } yield } # Declare an entity # entity Controller { signals and processes } proc vhdl::entity {name block} { variable entities set fullName [NsQualify $name] set entities($fullName) $block } # Instantiate an entity # instance Controller -name CI -port { InsideSig MySig } # The port is a dictionary mapping ports to surrounding signals proc vhdl::instance {name args} { variable entities variable currentPortMap variable currentInstParent # Locate the entity set fullName [NsQualify $name] # Search down namespaces while {![info exists entities($fullName)]} { set ns [namespace qualifier $fullName] if {$ns eq ""} { return -code error "No such entity \"$name\"" } set ns2 [namespace qualifier $ns] set tail [namespace tail $fullName] set fullName ${ns2}::$tail } # Check arguments set opts(-name) [namespace tail $fullName] set opts(-port) "" foreach {opt val} $args { set opts($opt) $val } # Figure out namespaces set parent [uplevel 1 namespace current] if {$parent eq "::"} { set newNs ${parent}$opts(-name) } else { set newNs ${parent}::$opts(-name) } set currentInstParent $parent set currentPortMap $opts(-port) if {[namespace exists $newNs]} { return -code error "Instance $opts(-name) already exists" } namespace eval $newNs $entities($fullName) # These variables are only active during an instantiation set currentInstParent "" set currentPortMap "" } # A port declaration in an entity automatically connects to # instantiating namespace's signal proc vhdl::port {name} { variable currentPortMap variable currentInstParent set parentName $name if {[dict exists $currentPortMap $name]} { set parentName [dict get $currentPortMap $name] } set currNs [uplevel 1 namespace current] namespace eval $currNs [list \ namespace upvar $currentInstParent $parentName $name] # Declare it as a signal as well # Use tailcall so signal can find the caller's namespace tailcall vhdl::signal $name } # Declare signal, with an optional initialization value. proc vhdl::signal {name args} { variable signalWait variable signalValue variable nsSignals set fullName [NsQualify $name] set ns [namespace qualifier $fullName] if {$ns eq ""} {set ns ::} lappend nsSignals($ns) $fullName set signalWait($fullName) {} set signalValue($fullName) {} upvar \#0 $fullName var if {[llength $args] > 0} { set var [lindex $args 0] set signalValue($fullName) [lindex $args 0] } trace add variable var write [list vhdl::WriteSig $fullName] } # Declare default reset signal proc vhdl::resetsignal {name} { variable defaultReset set defaultReset([uplevel 1 namespace current]) $name # Declare it as a signal as well # Use tailcall so signal can find the caller's namespace tailcall signal $name } # Declare process # process ?options? body # -clock Clk : Run process on rising edge of signal # -every Time : Run process regularly # -on SigList : Run process on signal change. # -reset Block : Code to run during reset. # -resetsig Sig : Reset signal proc vhdl::process {args} { variable coroCnt variable processTime variable processSignals variable now variable defaultReset variable signalValue set currNs [uplevel 1 namespace current] set body [lindex $args end] set args [lrange $args 0 end-1] set opts(-clock) "" set opts(-every) "" set opts(-on) "" set opts(-reset) "" if {![info exists defaultReset($currNs)]} { set defaultReset($currNs) "" } set opts(-resetsig) $defaultReset($currNs) foreach {opt val} $args { set opts($opt) $val } # A first yield to end the call at creation time set body2 "yield\n" # Make all signals available in this scope append body2 {global {*}[set ::vhdl::nsSignals([namespace current])]} \n append body2 "variable ::vhdl::now\n" # Put a loop around everything append body2 "while 1 \{\n" if {$opts(-clock) ne ""} { append body2 [string map [list %% $opts(-clock)] { while 1 { vhdl::wait on %% if {$%% == 1} break } }] } if {$opts(-every) ne ""} { append body2 "set __every__ \$now\n" } if {$opts(-reset) ne ""} { if {$opts(-resetsig) eq ""} { return -code error "No reset signal declared" } append body2 "if {\$$opts(-resetsig)} \{\n" append body2 $opts(-reset)\n append body2 "\} else \{\n" } append body2 $body\n if {$opts(-reset) ne ""} { append body2 "\}\n" } if {$opts(-every) ne ""} { append body2 "vhdl::wait for \[expr {$opts(-every) - (\$now - \$__every__)}\]\n" } if {$opts(-on) ne ""} { if {$opts(-on) eq "all"} { set vars {} foreach var [regexp -all -inline {\$\w+} $body] { set var [string range $var 1 end] set fullName [NsQualify $var] if {[info exists signalValue($fullName)]} { lappend vars $var } } if {[llength $vars] == 0} { return -code error "No vars detected in -on all process" } } else { set vars $opts(-on) } append body2 "[list vhdl::wait on $vars]\n" } # End of the loop around everything append body2 "\n\}\n" set name ::vhdl::process$coroCnt incr coroCnt coroutine $name apply [list {} $body2 $currNs] # The wakeup time is now, to make ut wake up immediately set processTime($name) $now set processSignals($name) {} }
############################################################################## # Examples ############################################################################## namespace import vhdl::* namespace import tcl::mathop::* signal Clk 1 signal Clk_N signal Gurka 0 signal Miffo 0 resetsignal Reset # Testing separation of namespaces namespace eval gurka { signal Clk signal Miffo 0 resetsignal Reset # Clock generator process -every 30 { set Clk 1 wait for 15 set Clk 0 if {$now > 250} wait } process { set Reset 1 wait for 200 set Reset 0 wait } process -clock Clk -reset { set Miffo 0 } { puts "CLK_N @$now Miffo $Miffo" incr Miffo } } # Clock generator process -every 20 { set Clk 1 wait for 10 set Clk 0 if {$now > 150} wait } # Clocked process process -clock Clk { puts "PROCESS1 @$now G=$Gurka" # Signals do not update immediately. The below should increment once incr Gurka incr Gurka # See that assignment is delayed puts " G=$Gurka" } process { puts "PROCESS2 @$now G=$Gurka" wait for 12 puts "PROCESS2 @$now G=$Gurka" wait on Gurka } # Combinatorial inverter process -on Clk { set Clk_N [! $Clk] } # Clocked process process -clock Clk_N -reset { set Miffo 0 } { puts "CLK_N @$now Miffo $Miffo" incr Miffo } # Startup reset generator process { set Reset 1 wait for 50 set Reset 0 wait } process -on {Gurka Miffo} { puts "G or M" } process -on all { puts "G $Gurka M $Miffo" } process { wait on Gurka for 7 puts "Gurka $Gurka @$now" if {$now > 150} wait } entity myent1 { port MySig process { puts "MySig 1 @$now" set MySig 1 wait for 10 puts "MySig 0 @$now" set MySig 0 wait for 10 if {$now > 150} wait } } namespace eval n2 { signal OtherSig signal OtherSig2 instance myent1 -name n1 -port { MySig OtherSig } instance myent1 -name n2 -port { MySig OtherSig2 } process { wait on OtherSig puts "OtherSig $OtherSig @$now" } process { wait on OtherSig2 puts "OtherSig2 $OtherSig2 @$now" } } # Enter event loop catch {console show} vwait forever