123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315(* A circuit is defined by transitively following the dependencies of its outputs,
stopping at unassigned wires or constants. [Signal_graph.inputs] does this.
All such unassigned wires are circuit inputs. As a consequence, all other wires
in a circuit are assigned, and hence cannot be changed. *)open!ImportmoduleUid_map=Signal.Uid_mapmoduleUid_set=Signal.Uid_setmoduleSignal_map=structtypet=Signal.tUid_map.t[@@derivingsexp_of]letcreategraph=letadd_signalmapsignal=letuid=Signal.uidsignalinMap.add_exnmap~key:uid~data:signalinSignal_graph.depth_first_searchgraph~init:Uid_map.empty~f_before:add_signal;;endtypet={name:string;signal_by_uid:Signal_map.t;inputs:Signal.tlist;outputs:Signal.tlist;phantom_inputs:(string*int)list;signal_graph:Signal_graph.t(* [fan_in] and [fan_out] are lazily computed. One might worry that this would interact
poorly with signals, which have some mutable components (e.g. wires). But those have
already been set by the time a circuit is created, so a circuit is not mutable. *);fan_out:Signal.Uid_set.tSignal.Uid_map.tLazy.t;fan_in:Signal.Uid_set.tSignal.Uid_map.tLazy.t}[@@derivingfields,sexp_of]moduleSummary=structletsexp_of_signalsignal=Signal.sexp_of_signal_recursive~depth:0signalletsexp_of_tt=[%message""~name:(t.name:string)~input_ports:(t.inputs:signallist)~output_ports:(t.outputs:signallist)];;endtype'awith_create_options=?detect_combinational_loops:bool->?normalize_uids:bool->'amoduleCreate_options=structtypet={detect_combinational_loops:booloption;normalize_uids:booloption}[@@derivingsexp_of]endletwith_create_optionsf?detect_combinational_loops?normalize_uids=f{Create_options.detect_combinational_loops;normalize_uids};;letcall_with_create_optionst{Create_options.detect_combinational_loops;normalize_uids}=t?detect_combinational_loops?normalize_uids;;letcreate_exn?(detect_combinational_loops=true)?(normalize_uids=true)~nameoutputs=letsignal_graph=Signal_graph.createoutputsin(* check that all outputs are assigned wires with 1 name *)ignore(ok_exn(Signal_graph.outputs~validate:truesignal_graph):Signal.tlist);(* uid normalization *)letsignal_graph=ifnormalize_uidsthenSignal_graph.normalize_uidssignal_graphelsesignal_graphin(* get new output wires *)letoutputs=Signal_graph.outputssignal_graph|>ok_exnin(* get inputs checking that they are valid *)letinputs=ok_exn(Signal_graph.inputssignal_graph)in(* check for combinational loops *)ifdetect_combinational_loopsthenok_exn(Signal_graph.detect_combinational_loopssignal_graph);(* construct the circuit *){name;signal_by_uid=Signal_map.createsignal_graph;inputs;outputs;phantom_inputs=[];signal_graph;fan_out=lazy(Signal_graph.fan_out_mapsignal_graph);fan_in=lazy(Signal_graph.fan_in_mapsignal_graph)};;letset_phantom_inputscircuitphantom_inputs=(* Remove phantom inputs that are already inputs, and disallow phantom inputs
that have the same name as an output. *)letmodulePort=structmoduleT=structtypet=string*int[@@derivingsexp_of]letcompare(n0,_)(n1,_)=String.comparen0n1endincludeTincludeComparable.Make(T)letof_signalport=letname=matchSignal.namesportwith|[name]->name|_->raise_s[%message"Ports should have one name"(port:Signal.t)(circuit:Summary.t)]inname,Signal.widthport;;endinletinputs=List.mapcircuit.inputs~f:Port.of_signal|>Set.of_list(modulePort)inletoutputs=List.mapcircuit.outputs~f:Port.of_signal|>Set.of_list(modulePort)inletphantom=Set.of_list(modulePort)phantom_inputsinletphantom_inputs=Set.diffphantominputsinifnot(Set.is_empty(Set.interphantom_inputsoutputs))thenraise_s[%message"Phantom input is also a circuit output"(phantom_inputs:Set.M(Port).t)(outputs:Set.M(Port).t)(circuit:Summary.t)];{circuitwithphantom_inputs=phantom_inputs|>Set.to_list};;letwith_namet~name={twithname}letuid_equalab=Int64.equal(Signal.uida)(Signal.uidb)letis_inputtsignal=List.memt.inputssignal~equal:uid_equalletis_outputtsignal=List.memt.outputssignal~equal:uid_equalletfind_signal_exntuid=Map.find_exnt.signal_by_uiduidletfan_out_mapt=Lazy.forcet.fan_outletfan_in_mapt=Lazy.forcet.fan_inletsignal_mapc=c.signal_by_uidletstructural_compare?check_namesc0c1=(* Number of inputs and outputs match *)tryList.length(outputsc0)=List.length(outputsc1)&&List.length(inputsc0)=List.length(inputsc1)(* outputs, including names, are the same *)&&List.fold2_exn(outputsc0)(outputsc1)~init:true~f:(funbo0o1->b&&[%compare.equal:stringlist](Signal.nameso0)(Signal.nameso1)&&Signal.widtho0=Signal.widtho1)(* inputs, including names, are the same *)&&List.fold2_exn(inputsc0)(inputsc1)~init:true~f:(funbi0i1->b&&[%compare.equal:stringlist](Signal.namesi0)(Signal.namesi1)&&Signal.widthi0=Signal.widthi1)&&(* check full structural comparision from each output *)snd(List.fold2_exn(outputsc0)(outputsc1)~init:(Uid_set.empty,true)~f:(fun(set,b)st->letset,b'=Signal.structural_compare?check_names~initial_deps:setstinset,b&&b'))with|Not_found_s_|Caml.Not_found->false;;modulePort_checks=structtypet=|Relaxed|Port_sets|Port_sets_and_widthsendmoduleWith_interface(I:Interface.S)(O:Interface.S)=structtypecreate=Signal.tInterface.Create_fn(I)(O).tletcheck_io_port_sets_matchcircuit=letactual_portsports=List.mapports~f:Signal.names|>List.concat|>Set.of_list(moduleString)inletactual_input_ports=inputscircuit|>actual_portsinletactual_input_ports=phantom_inputscircuit|>List.map~f:fst|>Set.of_list(moduleString)|>Set.unionactual_input_portsinletactual_output_ports=outputscircuit|>actual_portsinletexpected_input_ports=I.port_names|>I.to_list|>Set.of_list(moduleString)inletexpected_output_ports=O.port_names|>O.to_list|>Set.of_list(moduleString)inletcheckdirectionactual_portsexpected_ports=letexpected_but_not_in_circuit=Set.diffexpected_portsactual_portsinletin_circuit_but_not_expected=Set.diffactual_portsexpected_portsinif(not(Set.is_emptyexpected_but_not_in_circuit))||not(Set.is_emptyin_circuit_but_not_expected)thenraise_s[%message"Port sets do not match"(direction:string)(expected_ports:Set.M(String).t)(actual_ports:Set.M(String).t)(expected_but_not_in_circuit:Set.M(String).t)(in_circuit_but_not_expected:Set.M(String).t)(circuit:Summary.t)]incheck"input"actual_input_portsexpected_input_ports;check"output"actual_output_portsexpected_output_ports;;letcheck_widths_matchcircuit=letports=matchSignal.namesswith|[name]->name,Signal.widths|_->raise_s[%message"[Circuit.With_interface.check_widths_match] Unexpected error - invalid \
port name(s)"(circuit:Summary.t)]inletinputs=inputscircuit|>List.map~f:port|>I.of_alistinletoutputs=outputscircuit|>List.map~f:port|>O.of_alistinifnot(I.equalInt.equalinputsI.port_widths)thenraise_s[%message"Input port widths do not match"~expected:(I.port_widths:intI.t)~got:(inputs:intI.t)(circuit:Summary.t)];ifnot(O.equalInt.equaloutputsO.port_widths)thenraise_s[%message"Output port widths do not match"~expected:(O.port_widths:intO.t)~got:(outputs:intO.t)(circuit:Summary.t)];;letcheck_io_port_sets_and_widths_matchcircuit=check_io_port_sets_matchcircuit;check_widths_matchcircuit;;letmove_port_attributesfrom_to_=(* Wrap exceptions in case we have an empty signal. *)tryletfrom_=Signal.signal_idfrom_inletto_=Signal.signal_idto_into_.s_attributes<-from_.s_attributes;from_.s_attributes<-[]with|_->();;letcreate_exn=with_create_options(funcreate_options?(port_checks=Port_checks.Relaxed)?(add_phantom_inputs=true)~namelogic->letcircuit_inputs=I.mapI.t~f:(fun(n,b)->Signal.inputnb)inletinputs=I.mapcircuit_inputs~f:Signal.wireofinletoutputs=logicinputsinletcircuit_outputs=O.map2O.toutputs~f:(fun(n,_)s->Signal.outputns)inI.iter2inputscircuit_inputs~f:move_port_attributes;O.iter2outputscircuit_outputs~f:move_port_attributes;letcircuit=call_with_create_optionscreate_exncreate_options~name(O.to_listcircuit_outputs)inletcircuit=ifadd_phantom_inputsthenset_phantom_inputscircuit(I.to_listI.t)elsecircuitin(matchport_checkswith|Relaxed->()|Port_sets->check_io_port_sets_matchcircuit|Port_sets_and_widths->check_io_port_sets_and_widths_matchcircuit);circuit);;end