From 61add010583c632a7051ccc12e258344266d8800 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 30 Aug 2025 17:08:37 -0700 Subject: [PATCH 01/13] Create RampLimiter.kicad_sch --- .../resources/RampLimiter.kicad_sch | 1143 +++++++++++++++++ 1 file changed, 1143 insertions(+) create mode 100644 edg/abstract_parts/resources/RampLimiter.kicad_sch diff --git a/edg/abstract_parts/resources/RampLimiter.kicad_sch b/edg/abstract_parts/resources/RampLimiter.kicad_sch new file mode 100644 index 000000000..3f01e2ccc --- /dev/null +++ b/edg/abstract_parts/resources/RampLimiter.kicad_sch @@ -0,0 +1,1143 @@ +(kicad_sch + (version 20231120) + (generator "eeschema") + (generator_version "8.0") + (uuid "b55f6c44-5d5d-4524-bb86-893662f64598") + (paper "A4") + (lib_symbols + (symbol "Device:C" + (pin_numbers hide) + (pin_names + (offset 0.254) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "C" + (at 0.635 2.54 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "C" + (at 0.635 -2.54 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "" + (at 0.9652 -3.81 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Unpolarized capacitor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "cap capacitor" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_fp_filters" "C_*" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "C_0_1" + (polyline + (pts + (xy -2.032 -0.762) (xy 2.032 -0.762) + ) + (stroke + (width 0.508) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy -2.032 0.762) (xy 2.032 0.762) + ) + (stroke + (width 0.508) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "C_1_1" + (pin passive line + (at 0 3.81 270) + (length 2.794) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -3.81 90) + (length 2.794) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + ) + (symbol "Device:Q_PMOS_DGS" + (pin_numbers hide) + (pin_names + (offset 0) hide) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "Q" + (at 5.08 1.27 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "Q_PMOS_DGS" + (at 5.08 -1.27 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "" + (at 5.08 2.54 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "P-MOSFET transistor, drain/gate/source" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "transistor PMOS P-MOS P-MOSFET" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "Q_PMOS_DGS_0_1" + (polyline + (pts + (xy 0.254 0) (xy -2.54 0) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0.254 1.905) (xy 0.254 -1.905) + ) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0.762 -1.27) (xy 0.762 -2.286) + ) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0.762 0.508) (xy 0.762 -0.508) + ) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0.762 2.286) (xy 0.762 1.27) + ) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 2.54 2.54) (xy 2.54 1.778) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 2.54 -2.54) (xy 2.54 0) (xy 0.762 0) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0.762 1.778) (xy 3.302 1.778) (xy 3.302 -1.778) (xy 0.762 -1.778) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 2.286 0) (xy 1.27 0.381) (xy 1.27 -0.381) (xy 2.286 0) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type outline) + ) + ) + (polyline + (pts + (xy 2.794 -0.508) (xy 2.921 -0.381) (xy 3.683 -0.381) (xy 3.81 -0.254) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 3.302 -0.381) (xy 2.921 0.254) (xy 3.683 0.254) (xy 3.302 -0.381) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (circle + (center 1.651 0) + (radius 2.794) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (circle + (center 2.54 -1.778) + (radius 0.254) + (stroke + (width 0) + (type default) + ) + (fill + (type outline) + ) + ) + (circle + (center 2.54 1.778) + (radius 0.254) + (stroke + (width 0) + (type default) + ) + (fill + (type outline) + ) + ) + ) + (symbol "Q_PMOS_DGS_1_1" + (pin passive line + (at 2.54 5.08 270) + (length 2.54) + (name "D" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -5.08 0 0) + (length 2.54) + (name "G" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 2.54 -5.08 90) + (length 2.54) + (name "S" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "3" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + ) + (symbol "Device:VoltageDivider" + (pin_names + (offset 0) hide) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "RN" + (at -4.445 0 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "VoltageDivider" + (at -2.54 0 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 12.065 0 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 5.08 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Voltage divider" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "R network voltage divider" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_fp_filters" "R?Array?SIP* SOT?23" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "VoltageDivider_0_1" + (rectangle + (start -1.27 -3.81) + (end 1.27 3.81) + (stroke + (width 0.254) + (type default) + ) + (fill + (type background) + ) + ) + (rectangle + (start -0.508 -3.3782) + (end 0.508 -0.8382) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (rectangle + (start -0.508 3.3782) + (end 0.508 0.8382) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0 -5.08) (xy 0 -3.429) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0 0.889) (xy 0 -0.889) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0 5.08) (xy 0 3.3782) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 1.27 0) (xy 0 0) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (circle + (center 0 0.0254) + (radius 0.254) + (stroke + (width 0) + (type default) + ) + (fill + (type outline) + ) + ) + ) + (symbol "VoltageDivider_1_1" + (pin passive line + (at 0 6.35 270) + (length 2.54) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 3.81 0 180) + (length 2.54) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -6.35 90) + (length 2.54) + (name "~" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "3" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + ) + ) + (junction + (at 120.65 99.06) + (diameter 0) + (color 0 0 0 0) + (uuid "215f1c21-e47c-4a89-b3d1-4ddb3bb099da") + ) + (junction + (at 114.3 88.9) + (diameter 0) + (color 0 0 0 0) + (uuid "479f73d4-cc48-425a-a467-591dd9862f1e") + ) + (junction + (at 139.7 88.9) + (diameter 0) + (color 0 0 0 0) + (uuid "7556f988-a21a-4697-92b6-545cd4a43363") + ) + (junction + (at 120.65 88.9) + (diameter 0) + (color 0 0 0 0) + (uuid "a0e14f25-dffa-462d-b6f9-af9bebd20751") + ) + (junction + (at 132.08 99.06) + (diameter 0) + (color 0 0 0 0) + (uuid "e6b4e8a5-41a0-415d-a48a-d9c1e7e3526e") + ) + (wire + (pts + (xy 139.7 88.9) (xy 144.78 88.9) + ) + (stroke + (width 0) + (type default) + ) + (uuid "285f8327-7711-4ed1-a788-d1fa01757fc9") + ) + (wire + (pts + (xy 137.16 88.9) (xy 139.7 88.9) + ) + (stroke + (width 0) + (type default) + ) + (uuid "35671acd-6fc2-47ed-8006-8c17e13bac95") + ) + (wire + (pts + (xy 132.08 99.06) (xy 139.7 99.06) + ) + (stroke + (width 0) + (type default) + ) + (uuid "38e9e893-adb2-4227-9f5f-c77ed8290132") + ) + (wire + (pts + (xy 132.08 99.06) (xy 132.08 96.52) + ) + (stroke + (width 0) + (type default) + ) + (uuid "5d4a8428-852a-4366-9e72-c6ae299e0273") + ) + (wire + (pts + (xy 120.65 88.9) (xy 127 88.9) + ) + (stroke + (width 0) + (type default) + ) + (uuid "a91cf416-9e30-4146-a6e0-167e60675546") + ) + (wire + (pts + (xy 114.3 107.95) (xy 114.3 105.41) + ) + (stroke + (width 0) + (type default) + ) + (uuid "b7133c30-bf59-4b80-8c93-b172d825a6a5") + ) + (wire + (pts + (xy 109.22 88.9) (xy 114.3 88.9) + ) + (stroke + (width 0) + (type default) + ) + (uuid "be2560c1-ac04-43cf-a6f1-e143661234e2") + ) + (wire + (pts + (xy 114.3 88.9) (xy 120.65 88.9) + ) + (stroke + (width 0) + (type default) + ) + (uuid "c5e58f77-a0d8-45cf-b2a6-b44d6d4790c8") + ) + (wire + (pts + (xy 120.65 96.52) (xy 120.65 99.06) + ) + (stroke + (width 0) + (type default) + ) + (uuid "c9fccb91-0566-4da2-9d3d-606aa1d77044") + ) + (wire + (pts + (xy 118.11 99.06) (xy 120.65 99.06) + ) + (stroke + (width 0) + (type default) + ) + (uuid "d795db6b-5f25-4d40-a6cb-22f1f8c0613a") + ) + (wire + (pts + (xy 114.3 88.9) (xy 114.3 92.71) + ) + (stroke + (width 0) + (type default) + ) + (uuid "db6a5395-ca53-4afc-b807-6fc3f86df8ab") + ) + (wire + (pts + (xy 139.7 99.06) (xy 139.7 96.52) + ) + (stroke + (width 0) + (type default) + ) + (uuid "e0009e27-a210-4e69-95fc-ea8e4271b735") + ) + (wire + (pts + (xy 109.22 107.95) (xy 114.3 107.95) + ) + (stroke + (width 0) + (type default) + ) + (uuid "f5ddbd1a-e315-4726-9c83-5f690a16c9c5") + ) + (wire + (pts + (xy 120.65 99.06) (xy 132.08 99.06) + ) + (stroke + (width 0) + (type default) + ) + (uuid "fbc21c0c-33e6-4d70-b98b-f3b853c00b3a") + ) + (hierarchical_label "pwr" + (shape input) + (at 109.22 88.9 180) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + (uuid "7e701471-7566-4ec7-bc5d-01c4b5f88ebf") + ) + (hierarchical_label "output" + (shape output) + (at 144.78 88.9 0) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + (uuid "a47934db-1933-4fca-a88e-84db51cda995") + ) + (hierarchical_label "gnd" + (shape input) + (at 109.22 107.95 180) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + (uuid "ffdf205e-8c01-42f5-9c74-59c89b39b0dd") + ) + (symbol + (lib_id "Device:VoltageDivider") + (at 114.3 99.06 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "73986240-40bc-4e46-b80f-4e111070abd3") + (property "Reference" "div" + (at 111.76 98.4249 0) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + ) + (property "Value" "~" + (at 111.76 100.33 0) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + ) + (property "Footprint" "" + (at 126.365 99.06 90) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 119.38 99.06 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Voltage divider" + (at 114.3 99.06 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "2" + (uuid "3f7cc890-85b4-4db0-a897-cbed7844f3b4") + ) + (pin "3" + (uuid "236782bb-dd4b-493d-a65e-1f7c2406413d") + ) + (pin "1" + (uuid "d309cf0c-a0b4-4cc2-86fb-c23171b1d662") + ) + (instances + (project "" + (path "/b55f6c44-5d5d-4524-bb86-893662f64598" + (reference "div") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:C") + (at 139.7 92.71 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "c3693d33-43df-4ec5-a70f-fea37a52c7d6") + (property "Reference" "cgd" + (at 143.51 92.0749 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "~" + (at 143.51 93.98 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "" + (at 140.6652 96.52 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 139.7 92.71 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Unpolarized capacitor" + (at 139.7 92.71 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "2" + (uuid "22369ad6-5e6a-48e0-a8c1-6e031b88365a") + ) + (pin "1" + (uuid "26ea2e2f-6416-4eea-8846-24f773da1ceb") + ) + (instances + (project "" + (path "/b55f6c44-5d5d-4524-bb86-893662f64598" + (reference "cgd") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:C") + (at 120.65 92.71 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "ddb65cef-bb62-4169-b7b1-b45af4566831") + (property "Reference" "cgs" + (at 124.46 92.0749 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "~" + (at 124.46 93.98 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "" + (at 121.6152 96.52 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 120.65 92.71 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "Unpolarized capacitor" + (at 120.65 92.71 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "2" + (uuid "0ab5695d-c73c-47ab-b202-41dd6d22449b") + ) + (pin "1" + (uuid "77818459-a5a1-4a38-89be-382175a71233") + ) + (instances + (project "RampLimiter" + (path "/b55f6c44-5d5d-4524-bb86-893662f64598" + (reference "cgs") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:Q_PMOS_DGS") + (at 132.08 91.44 270) + (mirror x) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (uuid "ddd98509-3b5c-41ed-890f-7bff1485bfa3") + (property "Reference" "drv" + (at 134.62 93.98 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "~" + (at 132.08 82.55 90) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 134.62 86.36 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 132.08 91.44 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "" + (at 132.08 91.44 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "978cddad-6d95-4249-841d-016a7a164165") + ) + (pin "2" + (uuid "cae23339-625f-4e64-84d3-ad7a325d39d7") + ) + (pin "3" + (uuid "b36aa1fd-f3cc-4a68-854f-c7d29076a33b") + ) + (instances + (project "HighSideSwitch" + (path "/b55f6c44-5d5d-4524-bb86-893662f64598" + (reference "drv") + (unit 1) + ) + ) + ) + ) + (sheet_instances + (path "/" + (page "1") + ) + ) +) From 6f69192c8b44d6a992c9fbcbe123912cdfd06d6d Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 31 Aug 2025 03:22:43 -0700 Subject: [PATCH 02/13] docs --- edg/abstract_parts/PowerCircuits.py | 73 ++++++++++++++++++- .../resources/RampLimiter.kicad_sch | 8 +- 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/edg/abstract_parts/PowerCircuits.py b/edg/abstract_parts/PowerCircuits.py index 3fddbed61..a5e618dcd 100644 --- a/edg/abstract_parts/PowerCircuits.py +++ b/edg/abstract_parts/PowerCircuits.py @@ -1,8 +1,10 @@ from ..electronics_model import * from .Resettable import Resettable from .AbstractResistor import Resistor -from .AbstractFets import SwitchFet +from .AbstractFets import SwitchFet, Fet +from .AbstractCapacitor import Capacitor from .GateDrivers import HalfBridgeDriver, HalfBridgeDriverIndependent, HalfBridgeDriverPwm +from .ResistiveDivider import VoltageDivider from .Categories import PowerConditioner @@ -118,3 +120,72 @@ def generate(self): self.connect(self.pwm_ctl, self.driver.with_mixin(HalfBridgeDriverPwm()).pwm_in) if self.get(self.reset.is_connected()): self.connect(self.reset, self.driver.with_mixin(Resettable()).reset) + + +class RampLimiter(KiCadSchematicBlock): + """PMOS-based ramp limiter that roughly targets a constant-dV/dt ramp. + The cgd should be specified to swamp (10x+) the parasitic Cgd of the FET to get more controlled parameters. + The target ramp rate is in volts/second, and for a capacitive load this can be calulated from a target current with + I = C * dV/dt => dV/dt = I / C + The actual ramp rate will vary substantially, the values calculated are based on many assertions. + + A target Vgs can also be specified, this is the final Vgs of the FET after the ramp completes. + The FET will be constrained to have a Vgs,th below the minimum of this range and a Vgs,max above the maximum. + + A capacitive divider with Cgs will be generated so the target initial Vgs at less than half the FET Vgs,th + (targeting half Vgs,th at Vin,max). + + HOW THIS WORKS: + When the input voltage rises, the capacitive divider of Cgs, Cgd brings the gate to a subthreshold voltage. + The gate voltage charges via the divider until it gets to the threshold voltage. + At around the threshold voltage, the FET begins to turn on, with current flowing into (and charging) the output. + As the output rises, Cgd causes the gate to be pulled up with the output, keeping Vgs roughly constant. + (this also keeps the current roughly constant, mostly regardless of transconductance) + During this stage, if we assume Vgs is constant, then Cgs is constant and can be disregarded. + For the output to rise, Vgd must rise, which means Cgd must charge, and the current must go through the divider. + Assuming a constant Vgs (and absolute gate voltage), the current into the divider is constant, + and this is how the voltage ramp rate is controlled. + Once the output gets close to the input voltage, Cgd stops charging and Vgs rises, turning the FET fully on. + + Note that Vgs,th is an approximate parameter and the ramp current is likely larger than the Vgs,th current. + Vgs also may rise during the ramp, meaning some current goes into charging Cgs. + + References: https://www.ti.com/lit/an/slva156/slva156.pdf, https://www.ti.com/lit/an/slyt096/slyt096.pdf, + https://youtu.be/bOka13RtOXM + """ + @init_in_parent + def __init__(self, *, cgd: RangeLike = 10*nFarad(tol=0.5), target_ramp: RangeLike = 1000*Volt(tol=0.25), + target_vgs: RangeLike = (2.0, 3.0)*Volt, max_rds: FloatLike = 1*Ohm): + super().__init__() + + self.gnd = self.Port(Ground.empty(), [Common]) + self.pwr_in = self.Port(VoltageSink.empty(), [Input]) + self.pwr_out = self.Port(VoltageSource.empty(), [Output]) + + self.cgd = self.ArgParameter(cgd) + self.target_ramp = self.ArgParameter(target_ramp) + self.target_vgs = self.ArgParameter(target_vgs) + + def contents(self): + super().contents() + + pwr_voltage = self.pwr.link().voltage + self.drv = self.Block(Fet.PFet( + drain_voltage=pwr_voltage, + drain_current=self.pwr_out.link().current_drawn, + gate_voltage=(0 * Volt(tol=0)).hull(self.target_vgs.upper()), + rds_on=(0, self.max_rds), + power=(0, 0) * Watt # TODO size for through current at Rds,on + # TODO add Vgs,th spec + )) + + self.cap_gd = self.Block(Capacitor( + capacitance=self.cgd, + voltage=(0 * Volt(tol=0)).hull(self.pwr_in.link().voltage) + )) + self.cap_gs = self.Block(Capacitor( + # capacitance=self.cgd, # TODO calculate capacitive divider params + voltage=(0 * Volt(tol=0)).hull(self.pwr_in.link().voltage) + )) + self.div = self.Block(VoltageDivider(output_voltage=self.target_vgs, + impedance=...)) diff --git a/edg/abstract_parts/resources/RampLimiter.kicad_sch b/edg/abstract_parts/resources/RampLimiter.kicad_sch index 3f01e2ccc..b2f54105c 100644 --- a/edg/abstract_parts/resources/RampLimiter.kicad_sch +++ b/edg/abstract_parts/resources/RampLimiter.kicad_sch @@ -934,7 +934,7 @@ (dnp no) (fields_autoplaced yes) (uuid "c3693d33-43df-4ec5-a70f-fea37a52c7d6") - (property "Reference" "cgd" + (property "Reference" "cap_gd" (at 143.51 92.0749 0) (effects (font @@ -988,7 +988,7 @@ (instances (project "" (path "/b55f6c44-5d5d-4524-bb86-893662f64598" - (reference "cgd") + (reference "cap_gd") (unit 1) ) ) @@ -1004,7 +1004,7 @@ (dnp no) (fields_autoplaced yes) (uuid "ddb65cef-bb62-4169-b7b1-b45af4566831") - (property "Reference" "cgs" + (property "Reference" "cap_gs" (at 124.46 92.0749 0) (effects (font @@ -1058,7 +1058,7 @@ (instances (project "RampLimiter" (path "/b55f6c44-5d5d-4524-bb86-893662f64598" - (reference "cgs") + (reference "cap_gs") (unit 1) ) ) From 9cf80cd262bbed6892cce4ac427fe8a91d1c9ab4 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Mon, 1 Sep 2025 11:41:04 -0700 Subject: [PATCH 03/13] wip --- edg/abstract_parts/PowerCircuits.py | 5 +- edg/abstract_parts/resources/RampLimiter.asc | 58 ++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 edg/abstract_parts/resources/RampLimiter.asc diff --git a/edg/abstract_parts/PowerCircuits.py b/edg/abstract_parts/PowerCircuits.py index a5e618dcd..66234567d 100644 --- a/edg/abstract_parts/PowerCircuits.py +++ b/edg/abstract_parts/PowerCircuits.py @@ -125,7 +125,7 @@ def generate(self): class RampLimiter(KiCadSchematicBlock): """PMOS-based ramp limiter that roughly targets a constant-dV/dt ramp. The cgd should be specified to swamp (10x+) the parasitic Cgd of the FET to get more controlled parameters. - The target ramp rate is in volts/second, and for a capacitive load this can be calulated from a target current with + The target ramp rate is in volts/second, and for a capacitive load this can be calculated from a target current with I = C * dV/dt => dV/dt = I / C The actual ramp rate will vary substantially, the values calculated are based on many assertions. @@ -149,7 +149,7 @@ class RampLimiter(KiCadSchematicBlock): Note that Vgs,th is an approximate parameter and the ramp current is likely larger than the Vgs,th current. Vgs also may rise during the ramp, meaning some current goes into charging Cgs. - + References: https://www.ti.com/lit/an/slva156/slva156.pdf, https://www.ti.com/lit/an/slyt096/slyt096.pdf, https://youtu.be/bOka13RtOXM """ @@ -165,6 +165,7 @@ def __init__(self, *, cgd: RangeLike = 10*nFarad(tol=0.5), target_ramp: RangeLik self.cgd = self.ArgParameter(cgd) self.target_ramp = self.ArgParameter(target_ramp) self.target_vgs = self.ArgParameter(target_vgs) + self.max_rds = self.ArgParameter(max_rds) def contents(self): super().contents() diff --git a/edg/abstract_parts/resources/RampLimiter.asc b/edg/abstract_parts/resources/RampLimiter.asc new file mode 100644 index 000000000..e456ce670 --- /dev/null +++ b/edg/abstract_parts/resources/RampLimiter.asc @@ -0,0 +1,58 @@ +Version 4 +SHEET 1 880 680 +WIRE 48 128 -64 128 +WIRE 160 128 48 128 +WIRE 224 128 160 128 +WIRE 384 128 320 128 +WIRE 496 128 384 128 +WIRE 576 128 496 128 +WIRE 160 208 160 192 +WIRE 160 208 48 208 +WIRE 240 208 240 176 +WIRE 240 208 160 208 +WIRE 384 208 384 192 +WIRE 384 208 240 208 +WIRE 496 208 496 192 +WIRE 576 208 496 208 +WIRE 48 288 -64 288 +WIRE 496 304 496 208 +WIRE -64 320 -64 288 +FLAG -64 320 0 +FLAG 576 128 Vout +FLAG -64 128 Vin +FLAG 240 208 Vg +FLAG 496 304 0 +SYMBOL cap 144 128 R0 +SYMATTR InstName C1 +SYMATTR Value 470n +SYMBOL cap 368 128 R0 +SYMATTR InstName C2 +SYMATTR Value 10n +SYMBOL pmos 320 176 M270 +SYMATTR InstName M1 +SYMATTR Value AO6407 +SYMBOL res 32 112 R0 +SYMATTR InstName R1 +SYMATTR Value 200k +SYMBOL res 32 192 R0 +SYMATTR InstName R2 +SYMATTR Value 200k +SYMBOL voltage -64 192 R0 +WINDOW 3 -363 77 Left 2 +WINDOW 123 0 0 Left 0 +WINDOW 39 0 0 Left 0 +SYMATTR InstName V1 +SYMATTR Value PULSE(0 12 0 0.000001 0 1) +SYMBOL res 560 112 R0 +SYMATTR InstName R3 +SYMATTR Value 1000 +SYMBOL cap 480 128 R0 +SYMATTR InstName C3 +SYMATTR Value 200µ +SYMBOL voltage -64 112 R0 +WINDOW 3 -363 78 Left 2 +WINDOW 123 0 0 Left 0 +WINDOW 39 0 0 Left 0 +SYMATTR InstName V2 +SYMATTR Value PULSE(0 8 .050 .000001 0 1) +TEXT -194 330 Left 2 !.tran .1 From 624ff80f0b02a9ef56e0e93dd07ff7026ee652c0 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Mon, 1 Sep 2025 15:29:31 -0700 Subject: [PATCH 04/13] wip gate threshold spec and top level test --- edg/abstract_parts/AbstractFets.py | 23 +- edg/abstract_parts/PowerCircuits.py | 26 +- edg/abstract_parts/ResistiveDivider.py | 18 +- edg/abstract_parts/resources/RampLimiter.asc | 18 +- .../resources/RampLimiter.kicad_sch | 395 +++++++++++++++++- edg/abstract_parts/test_power_circuits.py | 23 + 6 files changed, 473 insertions(+), 30 deletions(-) create mode 100644 edg/abstract_parts/test_power_circuits.py diff --git a/edg/abstract_parts/AbstractFets.py b/edg/abstract_parts/AbstractFets.py index bc6b56df4..c565d26b4 100644 --- a/edg/abstract_parts/AbstractFets.py +++ b/edg/abstract_parts/AbstractFets.py @@ -53,6 +53,12 @@ class Fet(KiCadImportableBlock, DiscreteSemiconductor, HasStandardFootprint): """Base class for untyped MOSFETs Drain voltage, drain current, and gate voltages are positive (absolute). + The gate voltage is only checked against maximum ratings. + Optionally, the gate threshold voltage can also be specified. + + The actual gate drive voltage is specified as (threshold voltage, gate drive voltage), where the top end of that + is either the voltage at Rds,on or the specified driving voltage level. + MOSFET equations - https://inst.eecs.berkeley.edu/~ee105/fa05/handouts/discussions/Discussion5.pdf (cutoff/linear/saturation regions) @@ -81,7 +87,8 @@ def PFet(*args, **kwargs) -> 'Fet': @init_in_parent def __init__(self, drain_voltage: RangeLike, drain_current: RangeLike, *, - gate_voltage: RangeLike = (0, 0), rds_on: RangeLike = Range.all(), + gate_voltage: RangeLike = (0, 0), gate_threshold_voltage: RangeLike = Range.all(), + rds_on: RangeLike = Range.all(), gate_charge: RangeLike = Range.all(), power: RangeLike = Range.exact(0), channel: StringLike = StringExpr()) -> None: super().__init__() @@ -93,6 +100,7 @@ def __init__(self, drain_voltage: RangeLike, drain_current: RangeLike, *, self.drain_voltage = self.ArgParameter(drain_voltage) self.drain_current = self.ArgParameter(drain_current) self.gate_voltage = self.ArgParameter(gate_voltage) + self.gate_threshold_voltage = self.ArgParameter(gate_threshold_voltage) self.rds_on = self.ArgParameter(rds_on) self.gate_charge = self.ArgParameter(gate_charge) self.power = self.ArgParameter(power) @@ -142,8 +150,8 @@ class TableFet(PartsTableSelector, BaseTableFet): @init_in_parent def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.generator_param(self.drain_voltage, self.drain_current, self.gate_voltage, self.rds_on, self.gate_charge, - self.power, self.channel) + self.generator_param(self.drain_voltage, self.drain_current, self.gate_voltage, self.gate_threshold_voltage, + self.rds_on, self.gate_charge, self.power, self.channel) def _row_filter(self, row: PartsTableRow) -> bool: return super()._row_filter(row) and \ @@ -151,6 +159,7 @@ def _row_filter(self, row: PartsTableRow) -> bool: self.get(self.drain_voltage).fuzzy_in(row[self.VDS_RATING]) and \ self.get(self.drain_current).fuzzy_in(row[self.IDS_RATING]) and \ self.get(self.gate_voltage).fuzzy_in(row[self.VGS_RATING]) and \ + row[self.VGS_DRIVE].lower.fuzzy_in(self.get(self.gate_threshold_voltage)) and \ row[self.RDS_ON].fuzzy_in(self.get(self.rds_on)) and \ row[self.GATE_CHARGE].fuzzy_in(self.get(self.gate_charge)) and \ self.get(self.power).fuzzy_in(row[self.POWER_RATING]) @@ -184,7 +193,7 @@ def PFet(*args, **kwargs): @init_in_parent - def __init__(self, frequency: RangeLike, drive_current: RangeLike, **kwargs) -> None: + def __init__(self, *, frequency: RangeLike = 0*Hertz(tol=0), drive_current: RangeLike, **kwargs) -> None: super().__init__(**kwargs) self.frequency = self.ArgParameter(frequency) @@ -200,8 +209,9 @@ class TableSwitchFet(PartsTableSelector, SwitchFet, BaseTableFet): @init_in_parent def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.generator_param(self.frequency, self.drain_voltage, self.drain_current, self.gate_voltage, self.rds_on, - self.gate_charge, self.power, self.channel, self.drive_current) + self.generator_param(self.frequency, self.drain_voltage, self.drain_current, + self.gate_voltage, self.gate_threshold_voltage, + self.rds_on, self.gate_charge, self.power, self.channel, self.drive_current) self.actual_static_power = self.Parameter(RangeExpr()) self.actual_switching_power = self.Parameter(RangeExpr()) @@ -213,6 +223,7 @@ def _row_filter(self, row: PartsTableRow) -> bool: # here this is just a pre-fi self.get(self.drain_voltage).fuzzy_in(row[self.VDS_RATING]) and \ self.get(self.drain_current).fuzzy_in(row[self.IDS_RATING]) and \ self.get(self.gate_voltage).fuzzy_in(row[self.VGS_RATING]) and \ + row[self.VGS_DRIVE].lower.fuzzy_in(self.get(self.gate_threshold_voltage)) and \ row[self.RDS_ON].fuzzy_in(self.get(self.rds_on)) and \ row[self.GATE_CHARGE].fuzzy_in(self.get(self.gate_charge)) and \ self.get(self.power).fuzzy_in(row[self.POWER_RATING]) diff --git a/edg/abstract_parts/PowerCircuits.py b/edg/abstract_parts/PowerCircuits.py index 66234567d..881768f39 100644 --- a/edg/abstract_parts/PowerCircuits.py +++ b/edg/abstract_parts/PowerCircuits.py @@ -152,6 +152,9 @@ class RampLimiter(KiCadSchematicBlock): References: https://www.ti.com/lit/an/slva156/slva156.pdf, https://www.ti.com/lit/an/slyt096/slyt096.pdf, https://youtu.be/bOka13RtOXM + + Additional more complex circuits + https://electronics.stackexchange.com/questions/294061/p-channel-mosfet-inrush-current-limiting """ @init_in_parent def __init__(self, *, cgd: RangeLike = 10*nFarad(tol=0.5), target_ramp: RangeLike = 1000*Volt(tol=0.25), @@ -161,6 +164,7 @@ def __init__(self, *, cgd: RangeLike = 10*nFarad(tol=0.5), target_ramp: RangeLik self.gnd = self.Port(Ground.empty(), [Common]) self.pwr_in = self.Port(VoltageSink.empty(), [Input]) self.pwr_out = self.Port(VoltageSource.empty(), [Output]) + self.control = self.Port(DigitalSink.empty()) self.cgd = self.ArgParameter(cgd) self.target_ramp = self.ArgParameter(target_ramp) @@ -170,14 +174,14 @@ def __init__(self, *, cgd: RangeLike = 10*nFarad(tol=0.5), target_ramp: RangeLik def contents(self): super().contents() - pwr_voltage = self.pwr.link().voltage + pwr_voltage = self.pwr_in.link().voltage self.drv = self.Block(Fet.PFet( drain_voltage=pwr_voltage, drain_current=self.pwr_out.link().current_drawn, gate_voltage=(0 * Volt(tol=0)).hull(self.target_vgs.upper()), + gate_threshold_voltage=self.target_vgs, rds_on=(0, self.max_rds), power=(0, 0) * Watt # TODO size for through current at Rds,on - # TODO add Vgs,th spec )) self.cap_gd = self.Block(Capacitor( @@ -188,5 +192,19 @@ def contents(self): # capacitance=self.cgd, # TODO calculate capacitive divider params voltage=(0 * Volt(tol=0)).hull(self.pwr_in.link().voltage) )) - self.div = self.Block(VoltageDivider(output_voltage=self.target_vgs, - impedance=...)) + self.div = self.Block(VoltageDivider(output_voltage=self.target_vgs + # TODO specify impedance + )) + self.ctl_fet = self.Block(SwitchFet.NFet( + drain_voltage=pwr_voltage, + drain_current=(0, 1), # TODO + gate_voltage=(self.control.link().output_thresholds.upper(), self.control.link().voltage.upper()), + rds_on=(0, 1), # TODO size on turnon time + drive_current=self.control.link().current_limits # TODO this is kind of a max drive current + )) + + self.import_kicad( + self.file_path("resources", f"{self.__class__.__name__}.kicad_sch"), + conversions={ + }) + diff --git a/edg/abstract_parts/ResistiveDivider.py b/edg/abstract_parts/ResistiveDivider.py index d3b403a3f..37c86315d 100644 --- a/edg/abstract_parts/ResistiveDivider.py +++ b/edg/abstract_parts/ResistiveDivider.py @@ -1,7 +1,7 @@ from __future__ import annotations from math import log10, ceil -from typing import List, Tuple +from typing import List, Tuple, Mapping from ..electronics_model import * from . import Analog, Resistor @@ -55,8 +55,12 @@ def intersects(self, spec: 'DividerValues') -> bool: self.parallel_impedance.intersects(spec.parallel_impedance) -class ResistiveDivider(InternalSubcircuit, GeneratorBlock): +class ResistiveDivider(InternalSubcircuit, KiCadImportableBlock, GeneratorBlock): """Abstract, untyped (Passive) resistive divider, that takes in a ratio and parallel impedance spec.""" + def symbol_pinning(self, symbol_name: str) -> Mapping[str, BasePort]: + assert symbol_name == 'Device:VoltageDivider' + return {'1': self.top, '2': self.center, '3': self.bottom} + @classmethod def divider_ratio(cls, rtop: RangeExpr, rbot: RangeExpr) -> RangeExpr: """Calculates the output voltage of a resistive divider given the input voltages and resistances.""" @@ -131,12 +135,16 @@ def generate(self) -> None: @non_library -class BaseVoltageDivider(Block): +class BaseVoltageDivider(KiCadImportableBlock): """Base class that defines a resistive divider that takes in a voltage source and ground, and outputs an analog constant-voltage signal. The actual output voltage is defined as a ratio of the input voltage, and the divider is specified by ratio and impedance. Subclasses should define the ratio and impedance spec.""" + def symbol_pinning(self, symbol_name: str) -> Mapping[str, BasePort]: + assert symbol_name == 'Device:VoltageDivider' + return {'1': self.input, '2': self.output, '3': self.gnd} + @init_in_parent def __init__(self, impedance: RangeLike) -> None: super().__init__() @@ -218,6 +226,10 @@ def contents(self) -> None: class SignalDivider(Analog, Block): """Specialization of ResistiveDivider for Analog signals""" + def symbol_pinning(self, symbol_name: str) -> Mapping[str, BasePort]: + assert symbol_name == 'Device:VoltageDivider' + return {'1': self.input, '2': self.output, '3': self.gnd} + @init_in_parent def __init__(self, ratio: RangeLike, impedance: RangeLike) -> None: super().__init__() diff --git a/edg/abstract_parts/resources/RampLimiter.asc b/edg/abstract_parts/resources/RampLimiter.asc index e456ce670..8d6dff830 100644 --- a/edg/abstract_parts/resources/RampLimiter.asc +++ b/edg/abstract_parts/resources/RampLimiter.asc @@ -14,14 +14,16 @@ WIRE 384 208 384 192 WIRE 384 208 240 208 WIRE 496 208 496 192 WIRE 576 208 496 208 -WIRE 48 288 -64 288 WIRE 496 304 496 208 -WIRE -64 320 -64 288 -FLAG -64 320 0 +WIRE -64 384 -64 288 +WIRE 48 384 -64 384 +WIRE -64 432 -64 384 +FLAG -64 432 0 FLAG 576 128 Vout FLAG -64 128 Vin FLAG 240 208 Vg FLAG 496 304 0 +FLAG 0 448 0 SYMBOL cap 144 128 R0 SYMATTR InstName C1 SYMATTR Value 470n @@ -54,5 +56,13 @@ WINDOW 3 -363 78 Left 2 WINDOW 123 0 0 Left 0 WINDOW 39 0 0 Left 0 SYMATTR InstName V2 -SYMATTR Value PULSE(0 8 .050 .000001 0 1) +SYMATTR Value PULSE(0 8 0.050 .000001 0 1) +SYMBOL nmos 0 288 R0 +SYMATTR InstName M2 +SYMATTR Value AO6408 +SYMBOL voltage 0 352 R0 +WINDOW 123 0 0 Left 0 +WINDOW 39 0 0 Left 0 +SYMATTR InstName V3 +SYMATTR Value PULSE(3.3 0 .01 .000001 .000001 .045) TEXT -194 330 Left 2 !.tran .1 diff --git a/edg/abstract_parts/resources/RampLimiter.kicad_sch b/edg/abstract_parts/resources/RampLimiter.kicad_sch index b2f54105c..e408ca118 100644 --- a/edg/abstract_parts/resources/RampLimiter.kicad_sch +++ b/edg/abstract_parts/resources/RampLimiter.kicad_sch @@ -141,6 +141,290 @@ ) ) ) + (symbol "Device:Q_NMOS_DGS" + (pin_names + (offset 0) hide) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (property "Reference" "Q" + (at 5.08 1.27 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "Q_NMOS_DGS" + (at 5.08 -1.27 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "" + (at 5.08 2.54 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "N-MOSFET transistor, drain/gate/source" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "ki_keywords" "transistor NMOS N-MOS N-MOSFET" + (at 0 0 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (symbol "Q_NMOS_DGS_0_1" + (polyline + (pts + (xy 0.254 0) (xy -2.54 0) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0.254 1.905) (xy 0.254 -1.905) + ) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0.762 -1.27) (xy 0.762 -2.286) + ) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0.762 0.508) (xy 0.762 -0.508) + ) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0.762 2.286) (xy 0.762 1.27) + ) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 2.54 2.54) (xy 2.54 1.778) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 2.54 -2.54) (xy 2.54 0) (xy 0.762 0) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 0.762 -1.778) (xy 3.302 -1.778) (xy 3.302 1.778) (xy 0.762 1.778) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 1.016 0) (xy 2.032 0.381) (xy 2.032 -0.381) (xy 1.016 0) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type outline) + ) + ) + (polyline + (pts + (xy 2.794 0.508) (xy 2.921 0.381) (xy 3.683 0.381) (xy 3.81 0.254) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 3.302 0.381) (xy 2.921 -0.254) (xy 3.683 -0.254) (xy 3.302 0.381) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (circle + (center 1.651 0) + (radius 2.794) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (circle + (center 2.54 -1.778) + (radius 0.254) + (stroke + (width 0) + (type default) + ) + (fill + (type outline) + ) + ) + (circle + (center 2.54 1.778) + (radius 0.254) + (stroke + (width 0) + (type default) + ) + (fill + (type outline) + ) + ) + ) + (symbol "Q_NMOS_DGS_1_1" + (pin passive line + (at 2.54 5.08 270) + (length 2.54) + (name "D" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -5.08 0 0) + (length 2.54) + (name "G" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 2.54 -5.08 90) + (length 2.54) + (name "S" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "3" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + ) (symbol "Device:Q_PMOS_DGS" (pin_numbers hide) (pin_names @@ -675,6 +959,16 @@ (color 0 0 0 0) (uuid "e6b4e8a5-41a0-415d-a48a-d9c1e7e3526e") ) + (wire + (pts + (xy 109.22 118.11) (xy 114.3 118.11) + ) + (stroke + (width 0) + (type default) + ) + (uuid "0358eb6f-380f-495b-a342-1d09e339832b") + ) (wire (pts (xy 139.7 88.9) (xy 144.78 88.9) @@ -795,16 +1089,6 @@ ) (uuid "e0009e27-a210-4e69-95fc-ea8e4271b735") ) - (wire - (pts - (xy 109.22 107.95) (xy 114.3 107.95) - ) - (stroke - (width 0) - (type default) - ) - (uuid "f5ddbd1a-e315-4726-9c83-5f690a16c9c5") - ) (wire (pts (xy 120.65 99.06) (xy 132.08 99.06) @@ -815,7 +1099,7 @@ ) (uuid "fbc21c0c-33e6-4d70-b98b-f3b853c00b3a") ) - (hierarchical_label "pwr" + (hierarchical_label "pwr_in" (shape input) (at 109.22 88.9 180) (fields_autoplaced yes) @@ -827,7 +1111,7 @@ ) (uuid "7e701471-7566-4ec7-bc5d-01c4b5f88ebf") ) - (hierarchical_label "output" + (hierarchical_label "pwr_out" (shape output) (at 144.78 88.9 0) (fields_autoplaced yes) @@ -839,9 +1123,21 @@ ) (uuid "a47934db-1933-4fca-a88e-84db51cda995") ) + (hierarchical_label "control" + (shape input) + (at 106.68 113.03 180) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + (uuid "b5dfa70c-f6e7-4009-b0d1-119c4fd8669c") + ) (hierarchical_label "gnd" (shape input) - (at 109.22 107.95 180) + (at 109.22 118.11 180) (fields_autoplaced yes) (effects (font @@ -851,6 +1147,79 @@ ) (uuid "ffdf205e-8c01-42f5-9c74-59c89b39b0dd") ) + (symbol + (lib_id "Device:Q_NMOS_DGS") + (at 111.76 113.03 0) + (unit 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (dnp no) + (fields_autoplaced yes) + (uuid "1662ba23-bc3e-4494-9821-c30a52078ad7") + (property "Reference" "ctl_fet" + (at 118.11 112.3949 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "~" + (at 118.11 114.3 0) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "" + (at 116.84 110.49 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Datasheet" "~" + (at 111.76 113.03 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (property "Description" "N-MOSFET transistor, drain/gate/source" + (at 111.76 113.03 0) + (effects + (font + (size 1.27 1.27) + ) + (hide yes) + ) + ) + (pin "1" + (uuid "50faf460-9b5a-44d5-8c92-713b033bf5dd") + ) + (pin "2" + (uuid "92a76b69-eff5-42e2-b690-1619fcfda2c8") + ) + (pin "3" + (uuid "67b9dadd-d40e-4d04-b0a7-dba6cc26f742") + ) + (instances + (project "" + (path "/b55f6c44-5d5d-4524-bb86-893662f64598" + (reference "ctl_fet") + (unit 1) + ) + ) + ) + ) (symbol (lib_id "Device:VoltageDivider") (at 114.3 99.06 0) diff --git a/edg/abstract_parts/test_power_circuits.py b/edg/abstract_parts/test_power_circuits.py new file mode 100644 index 000000000..347b249d5 --- /dev/null +++ b/edg/abstract_parts/test_power_circuits.py @@ -0,0 +1,23 @@ +import unittest + +from . import * +from .DummyDevices import DummyVoltageSource +from .PowerCircuits import RampLimiter + + +class RampLimiterTestTop(Block): + def __init__(self): + super().__init__() + self.dut = self.Block(RampLimiter()) + + (self.dummyin, ), _ = self.chain(self.dut.pwr_in, self.Block(DummyVoltageSource(voltage_out=12*Volt(tol=0)))) + (self.dummyout, ), _ = self.chain(self.dut.pwr_out, self.Block(DummyVoltageSink(current_draw=1*Amp(tol=0)))) + (self.dummygnd, ), _ = self.chain(self.dut.gnd, self.Block(DummyGround())) + + +class RampLimiterTest(unittest.TestCase): + def test_opamp_amplifier(self) -> None: + compiled = ScalaCompiler.compile(RampLimiterTestTop, refinements=Refinements( + class_refinements=[ + ] + )) From d8978f5885265f680d45ff0feedfb96581706560 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Mon, 1 Sep 2025 15:39:32 -0700 Subject: [PATCH 05/13] wip towards test case --- edg/{parts => abstract_parts}/GenericCapacitor.py | 2 +- edg/{parts => abstract_parts}/GenericResistor.py | 2 +- edg/abstract_parts/PowerCircuits.py | 8 ++++++-- edg/abstract_parts/__init__.py | 3 +++ edg/abstract_parts/test_power_circuits.py | 2 ++ edg/parts/__init__.py | 2 -- edg/parts/test_capacitor_generic.py | 2 +- 7 files changed, 14 insertions(+), 7 deletions(-) rename edg/{parts => abstract_parts}/GenericCapacitor.py (99%) rename edg/{parts => abstract_parts}/GenericResistor.py (99%) diff --git a/edg/parts/GenericCapacitor.py b/edg/abstract_parts/GenericCapacitor.py similarity index 99% rename from edg/parts/GenericCapacitor.py rename to edg/abstract_parts/GenericCapacitor.py index 8ba8ef673..8b33f0a39 100644 --- a/edg/parts/GenericCapacitor.py +++ b/edg/abstract_parts/GenericCapacitor.py @@ -1,7 +1,7 @@ from typing import NamedTuple, Dict, Optional import math -from ..abstract_parts import * +from edg.abstract_parts import * class GenericMlcc(Capacitor, SelectorArea, FootprintBlock, GeneratorBlock): diff --git a/edg/parts/GenericResistor.py b/edg/abstract_parts/GenericResistor.py similarity index 99% rename from edg/parts/GenericResistor.py rename to edg/abstract_parts/GenericResistor.py index a0a8bde17..7e5a3f293 100644 --- a/edg/parts/GenericResistor.py +++ b/edg/abstract_parts/GenericResistor.py @@ -1,6 +1,6 @@ from typing import List, Tuple -from ..abstract_parts import * +from edg.abstract_parts import * @non_library diff --git a/edg/abstract_parts/PowerCircuits.py b/edg/abstract_parts/PowerCircuits.py index 881768f39..bf3865e17 100644 --- a/edg/abstract_parts/PowerCircuits.py +++ b/edg/abstract_parts/PowerCircuits.py @@ -4,7 +4,7 @@ from .AbstractFets import SwitchFet, Fet from .AbstractCapacitor import Capacitor from .GateDrivers import HalfBridgeDriver, HalfBridgeDriverIndependent, HalfBridgeDriverPwm -from .ResistiveDivider import VoltageDivider +from .ResistiveDivider import VoltageDivider, ResistiveDivider from .Categories import PowerConditioner @@ -192,7 +192,7 @@ def contents(self): # capacitance=self.cgd, # TODO calculate capacitive divider params voltage=(0 * Volt(tol=0)).hull(self.pwr_in.link().voltage) )) - self.div = self.Block(VoltageDivider(output_voltage=self.target_vgs + self.div = self.Block(ResistiveDivider(ratio=self.target_vgs.shrink_multiply(1/self.pwr_in.link().voltage) # TODO specify impedance )) self.ctl_fet = self.Block(SwitchFet.NFet( @@ -206,5 +206,9 @@ def contents(self): self.import_kicad( self.file_path("resources", f"{self.__class__.__name__}.kicad_sch"), conversions={ + 'pwr_in': VoltageSink(), + 'pwr_out': VoltageSource(), + 'control': DigitalSink(), + 'gnd': Ground(), }) diff --git a/edg/abstract_parts/__init__.py b/edg/abstract_parts/__init__.py index 725ff44dc..1b0c59380 100644 --- a/edg/abstract_parts/__init__.py +++ b/edg/abstract_parts/__init__.py @@ -106,6 +106,9 @@ from .PinMappable import PinResource, PeripheralFixedPin, PeripheralAnyResource, PeripheralFixedResource from .VariantPinRemapper import VariantPinRemapper +from .GenericResistor import ESeriesResistor, GenericChipResistor, GenericAxialResistor, GenericAxialVerticalResistor +from .GenericCapacitor import GenericMlcc + from .DummyDevices import DummyPassive, DummyGround, DummyVoltageSource, DummyVoltageSink, DummyDigitalSink, \ DummyAnalogSource, DummyAnalogSink from .DummyDevices import ForcedVoltageCurrentDraw, ForcedVoltageCurrentLimit, ForcedVoltage, ForcedVoltageCurrent, \ diff --git a/edg/abstract_parts/test_power_circuits.py b/edg/abstract_parts/test_power_circuits.py index 347b249d5..265243a58 100644 --- a/edg/abstract_parts/test_power_circuits.py +++ b/edg/abstract_parts/test_power_circuits.py @@ -19,5 +19,7 @@ class RampLimiterTest(unittest.TestCase): def test_opamp_amplifier(self) -> None: compiled = ScalaCompiler.compile(RampLimiterTestTop, refinements=Refinements( class_refinements=[ + (Resistor, GenericChipResistor), + (Capacitor, GenericMlcc), ] )) diff --git a/edg/parts/__init__.py b/edg/parts/__init__.py index a1c7e4358..01bf292be 100644 --- a/edg/parts/__init__.py +++ b/edg/parts/__init__.py @@ -3,10 +3,8 @@ from .JlcPart import JlcPart from .JlcBlackbox import KiCadJlcBlackbox -from .GenericResistor import ESeriesResistor, GenericChipResistor, GenericAxialResistor, GenericAxialVerticalResistor from .JlcResistor import JlcResistor from .JlcResistorArray import JlcResistorArray -from .GenericCapacitor import GenericMlcc from .JlcCapacitor import JlcCapacitor from .JlcElectrolyticCapacitor import JlcAluminumCapacitor from .JlcInductor import JlcInductor diff --git a/edg/parts/test_capacitor_generic.py b/edg/parts/test_capacitor_generic.py index 505e278c4..829a662e2 100644 --- a/edg/parts/test_capacitor_generic.py +++ b/edg/parts/test_capacitor_generic.py @@ -1,6 +1,6 @@ import unittest -from .GenericCapacitor import * +from edg.abstract_parts.GenericCapacitor import * class CapacitorGenericTestTop(Block): From 4fb6387ff06d1f3d9237c54ec2c4941589dd506f Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Mon, 1 Sep 2025 16:18:33 -0700 Subject: [PATCH 06/13] wip adding infrastructure --- edg/{parts => abstract_parts}/CustomDiode.py | 3 ++- edg/{parts => abstract_parts}/CustomFet.py | 5 +++-- edg/abstract_parts/GenericCapacitor.py | 5 ++++- edg/abstract_parts/GenericResistor.py | 5 ++++- edg/abstract_parts/__init__.py | 2 ++ edg/abstract_parts/test_power_circuits.py | 2 ++ edg/parts/__init__.py | 2 -- 7 files changed, 17 insertions(+), 7 deletions(-) rename edg/{parts => abstract_parts}/CustomDiode.py (94%) rename edg/{parts => abstract_parts}/CustomFet.py (90%) diff --git a/edg/parts/CustomDiode.py b/edg/abstract_parts/CustomDiode.py similarity index 94% rename from edg/parts/CustomDiode.py rename to edg/abstract_parts/CustomDiode.py index 6d0c2a03c..b8a066ee7 100644 --- a/edg/parts/CustomDiode.py +++ b/edg/abstract_parts/CustomDiode.py @@ -1,4 +1,5 @@ -from ..abstract_parts import * +from ..electronics_model import * +from .AbstractDiodes import Diode class CustomDiode(Diode, FootprintBlock, GeneratorBlock): diff --git a/edg/parts/CustomFet.py b/edg/abstract_parts/CustomFet.py similarity index 90% rename from edg/parts/CustomFet.py rename to edg/abstract_parts/CustomFet.py index 7b70559d5..c586bae45 100644 --- a/edg/parts/CustomFet.py +++ b/edg/abstract_parts/CustomFet.py @@ -1,7 +1,8 @@ -from ..abstract_parts import * +from ..electronics_model import * +from .AbstractFets import SwitchFet -class CustomFet(Fet, FootprintBlock, GeneratorBlock): +class CustomFet(SwitchFet, FootprintBlock, GeneratorBlock): @init_in_parent def __init__(self, *args, footprint_spec: StringLike = "", manufacturer_spec: StringLike = "", part_spec: StringLike = "", **kwargs): diff --git a/edg/abstract_parts/GenericCapacitor.py b/edg/abstract_parts/GenericCapacitor.py index 8b33f0a39..2608ff793 100644 --- a/edg/abstract_parts/GenericCapacitor.py +++ b/edg/abstract_parts/GenericCapacitor.py @@ -1,7 +1,10 @@ from typing import NamedTuple, Dict, Optional import math -from edg.abstract_parts import * +from ..electronics_model import * +from .AbstractCapacitor import Capacitor, DummyCapacitorFootprint +from .SelectorArea import SelectorArea +from .ESeriesUtil import ESeriesUtil class GenericMlcc(Capacitor, SelectorArea, FootprintBlock, GeneratorBlock): diff --git a/edg/abstract_parts/GenericResistor.py b/edg/abstract_parts/GenericResistor.py index 7e5a3f293..1431205e0 100644 --- a/edg/abstract_parts/GenericResistor.py +++ b/edg/abstract_parts/GenericResistor.py @@ -1,6 +1,9 @@ from typing import List, Tuple -from edg.abstract_parts import * +from ..electronics_model import * +from .AbstractResistor import Resistor +from .ESeriesUtil import ESeriesUtil +from .SelectorArea import SelectorArea @non_library diff --git a/edg/abstract_parts/__init__.py b/edg/abstract_parts/__init__.py index 1b0c59380..4157cae92 100644 --- a/edg/abstract_parts/__init__.py +++ b/edg/abstract_parts/__init__.py @@ -106,6 +106,8 @@ from .PinMappable import PinResource, PeripheralFixedPin, PeripheralAnyResource, PeripheralFixedResource from .VariantPinRemapper import VariantPinRemapper +from .CustomDiode import CustomDiode +from .CustomFet import CustomFet from .GenericResistor import ESeriesResistor, GenericChipResistor, GenericAxialResistor, GenericAxialVerticalResistor from .GenericCapacitor import GenericMlcc diff --git a/edg/abstract_parts/test_power_circuits.py b/edg/abstract_parts/test_power_circuits.py index 265243a58..a7b4a8442 100644 --- a/edg/abstract_parts/test_power_circuits.py +++ b/edg/abstract_parts/test_power_circuits.py @@ -21,5 +21,7 @@ def test_opamp_amplifier(self) -> None: class_refinements=[ (Resistor, GenericChipResistor), (Capacitor, GenericMlcc), + (Fet, CustomFet), + (SwitchFet, CustomFet), ] )) diff --git a/edg/parts/__init__.py b/edg/parts/__init__.py index 01bf292be..52f030bb5 100644 --- a/edg/parts/__init__.py +++ b/edg/parts/__init__.py @@ -14,8 +14,6 @@ from .JlcDiode import JlcDiode, JlcZenerDiode from .JlcBjt import JlcBjt from .JlcFet import JlcFet, JlcSwitchFet -from .CustomDiode import CustomDiode -from .CustomFet import CustomFet from .Batteries import Cr2032, Li18650, AaBattery, AaBatteryStack from .Switches import SmtSwitch, SmtSwitchRa, KailhSocket from .Joystick_Xbox import XboxElite2Joystick From cba7e861f9462e99fa5c30dc8aac8dcf357f609b Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Mon, 1 Sep 2025 16:21:31 -0700 Subject: [PATCH 07/13] wip --- edg/abstract_parts/AbstractFets.py | 4 ++-- edg/{parts => abstract_parts}/test_capacitor_generic.py | 2 +- .../test_resistor_generic.py} | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename edg/{parts => abstract_parts}/test_capacitor_generic.py (99%) rename edg/{parts/test_resistor.py => abstract_parts/test_resistor_generic.py} (98%) diff --git a/edg/abstract_parts/AbstractFets.py b/edg/abstract_parts/AbstractFets.py index c565d26b4..a2f41049b 100644 --- a/edg/abstract_parts/AbstractFets.py +++ b/edg/abstract_parts/AbstractFets.py @@ -159,7 +159,7 @@ def _row_filter(self, row: PartsTableRow) -> bool: self.get(self.drain_voltage).fuzzy_in(row[self.VDS_RATING]) and \ self.get(self.drain_current).fuzzy_in(row[self.IDS_RATING]) and \ self.get(self.gate_voltage).fuzzy_in(row[self.VGS_RATING]) and \ - row[self.VGS_DRIVE].lower.fuzzy_in(self.get(self.gate_threshold_voltage)) and \ + (row[self.VGS_DRIVE].lower in self.get(self.gate_threshold_voltage)) and \ row[self.RDS_ON].fuzzy_in(self.get(self.rds_on)) and \ row[self.GATE_CHARGE].fuzzy_in(self.get(self.gate_charge)) and \ self.get(self.power).fuzzy_in(row[self.POWER_RATING]) @@ -223,7 +223,7 @@ def _row_filter(self, row: PartsTableRow) -> bool: # here this is just a pre-fi self.get(self.drain_voltage).fuzzy_in(row[self.VDS_RATING]) and \ self.get(self.drain_current).fuzzy_in(row[self.IDS_RATING]) and \ self.get(self.gate_voltage).fuzzy_in(row[self.VGS_RATING]) and \ - row[self.VGS_DRIVE].lower.fuzzy_in(self.get(self.gate_threshold_voltage)) and \ + (row[self.VGS_DRIVE].lower in self.get(self.gate_threshold_voltage)) and \ row[self.RDS_ON].fuzzy_in(self.get(self.rds_on)) and \ row[self.GATE_CHARGE].fuzzy_in(self.get(self.gate_charge)) and \ self.get(self.power).fuzzy_in(row[self.POWER_RATING]) diff --git a/edg/parts/test_capacitor_generic.py b/edg/abstract_parts/test_capacitor_generic.py similarity index 99% rename from edg/parts/test_capacitor_generic.py rename to edg/abstract_parts/test_capacitor_generic.py index 829a662e2..f4aeada24 100644 --- a/edg/parts/test_capacitor_generic.py +++ b/edg/abstract_parts/test_capacitor_generic.py @@ -1,6 +1,6 @@ import unittest -from edg.abstract_parts.GenericCapacitor import * +from . import * class CapacitorGenericTestTop(Block): diff --git a/edg/parts/test_resistor.py b/edg/abstract_parts/test_resistor_generic.py similarity index 98% rename from edg/parts/test_resistor.py rename to edg/abstract_parts/test_resistor_generic.py index ea96f5776..5a75c0c74 100644 --- a/edg/parts/test_resistor.py +++ b/edg/abstract_parts/test_resistor_generic.py @@ -1,6 +1,6 @@ import unittest -from .GenericResistor import * +from . import * class ResistorTestTop(Block): From e761549ced842e3f12162cffe0f67e4c992802d9 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Mon, 1 Sep 2025 16:29:03 -0700 Subject: [PATCH 08/13] wip --- edg/abstract_parts/test_power_circuits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edg/abstract_parts/test_power_circuits.py b/edg/abstract_parts/test_power_circuits.py index a7b4a8442..073a16ff1 100644 --- a/edg/abstract_parts/test_power_circuits.py +++ b/edg/abstract_parts/test_power_circuits.py @@ -16,7 +16,7 @@ def __init__(self): class RampLimiterTest(unittest.TestCase): - def test_opamp_amplifier(self) -> None: + def test_ramp_limiter(self) -> None: compiled = ScalaCompiler.compile(RampLimiterTestTop, refinements=Refinements( class_refinements=[ (Resistor, GenericChipResistor), From fa7c97f86ced6c90dd9c3bd72111cb20cdea9179 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Mon, 1 Sep 2025 18:41:00 -0700 Subject: [PATCH 09/13] near full impl --- edg/abstract_parts/DummyDevices.py | 12 ++++++++ edg/abstract_parts/PowerCircuits.py | 35 ++++++++++++++++------- edg/abstract_parts/__init__.py | 4 +-- edg/abstract_parts/test_power_circuits.py | 20 +++++++++---- 4 files changed, 52 insertions(+), 19 deletions(-) diff --git a/edg/abstract_parts/DummyDevices.py b/edg/abstract_parts/DummyDevices.py index 4f0531611..ac6cb96b3 100644 --- a/edg/abstract_parts/DummyDevices.py +++ b/edg/abstract_parts/DummyDevices.py @@ -45,6 +45,18 @@ def __init__(self, voltage_limit: RangeLike = RangeExpr.ALL, self.current_limits = self.Parameter(RangeExpr(self.pwr.link().current_limits)) +class DummyDigitalSource(DummyDevice): + @init_in_parent + def __init__(self, voltage_out: RangeLike = RangeExpr.ZERO, + current_limits: RangeLike = RangeExpr.ALL) -> None: + super().__init__() + + self.io = self.Port(DigitalSource( + voltage_out=voltage_out, + current_limits=current_limits + ), [InOut]) + + class DummyDigitalSink(DummyDevice): @init_in_parent def __init__(self, voltage_limit: RangeLike = RangeExpr.ALL, diff --git a/edg/abstract_parts/PowerCircuits.py b/edg/abstract_parts/PowerCircuits.py index bf3865e17..d06b3b4f3 100644 --- a/edg/abstract_parts/PowerCircuits.py +++ b/edg/abstract_parts/PowerCircuits.py @@ -158,7 +158,8 @@ class RampLimiter(KiCadSchematicBlock): """ @init_in_parent def __init__(self, *, cgd: RangeLike = 10*nFarad(tol=0.5), target_ramp: RangeLike = 1000*Volt(tol=0.25), - target_vgs: RangeLike = (2.0, 3.0)*Volt, max_rds: FloatLike = 1*Ohm): + target_vgs: RangeLike = (2.0, 3.0)*Volt, max_rds: FloatLike = 1*Ohm, + _cdiv_vgs_factor: RangeLike = (0.02, 0.5)): super().__init__() self.gnd = self.Port(Ground.empty(), [Common]) @@ -170,6 +171,7 @@ def __init__(self, *, cgd: RangeLike = 10*nFarad(tol=0.5), target_ramp: RangeLik self.target_ramp = self.ArgParameter(target_ramp) self.target_vgs = self.ArgParameter(target_vgs) self.max_rds = self.ArgParameter(max_rds) + self._cdiv_vgs_factor = self.ArgParameter(_cdiv_vgs_factor) def contents(self): super().contents() @@ -188,26 +190,37 @@ def contents(self): capacitance=self.cgd, voltage=(0 * Volt(tol=0)).hull(self.pwr_in.link().voltage) )) + # treat Cgs and Cgd as a capacitive divider with Cgs on the bottom self.cap_gs = self.Block(Capacitor( - # capacitance=self.cgd, # TODO calculate capacitive divider params + capacitance=( + (1/(self.drv.actual_gate_drive.lower()*self._cdiv_vgs_factor)).shrink_multiply(self.pwr_in.link().voltage) - 1 + ).shrink_multiply( + self.cap_gd.actual_capacitance + ), voltage=(0 * Volt(tol=0)).hull(self.pwr_in.link().voltage) )) - self.div = self.Block(ResistiveDivider(ratio=self.target_vgs.shrink_multiply(1/self.pwr_in.link().voltage) - # TODO specify impedance - )) + # dV/dt over a capacitor is I / C => I = Cgd * dV/dt + # then calculate to get the target I: Vgs,th = I * Reff => Reff = Vgs,th / I = Vgs,th / (Cgd * dV/dt) + # we assume Vgs,th is exact, and only contributing sources come from elsewhere + self.div = self.Block(ResistiveDivider(ratio=self.target_vgs.shrink_multiply(1/self.pwr_in.link().voltage), + impedance=(1 / self.target_ramp).shrink_multiply(self.drv.actual_gate_drive.lower() / (self.cap_gd.actual_capacitance)) + )) + div_current_draw = (self.pwr_in.link().voltage/self.div.actual_impedance).hull(0) self.ctl_fet = self.Block(SwitchFet.NFet( drain_voltage=pwr_voltage, - drain_current=(0, 1), # TODO - gate_voltage=(self.control.link().output_thresholds.upper(), self.control.link().voltage.upper()), - rds_on=(0, 1), # TODO size on turnon time - drive_current=self.control.link().current_limits # TODO this is kind of a max drive current + drain_current=div_current_draw, + gate_voltage=(self.control.link().output_thresholds.upper(), self.control.link().voltage.upper()) )) self.import_kicad( self.file_path("resources", f"{self.__class__.__name__}.kicad_sch"), conversions={ - 'pwr_in': VoltageSink(), - 'pwr_out': VoltageSource(), + 'pwr_in': VoltageSink( + current_draw=self.pwr_out.link().current_drawn + div_current_draw + ), + 'pwr_out': VoltageSource( + voltage_out=self.pwr_in.link().voltage + ), 'control': DigitalSink(), 'gnd': Ground(), }) diff --git a/edg/abstract_parts/__init__.py b/edg/abstract_parts/__init__.py index 4157cae92..7b84ca00d 100644 --- a/edg/abstract_parts/__init__.py +++ b/edg/abstract_parts/__init__.py @@ -111,8 +111,8 @@ from .GenericResistor import ESeriesResistor, GenericChipResistor, GenericAxialResistor, GenericAxialVerticalResistor from .GenericCapacitor import GenericMlcc -from .DummyDevices import DummyPassive, DummyGround, DummyVoltageSource, DummyVoltageSink, DummyDigitalSink, \ - DummyAnalogSource, DummyAnalogSink +from .DummyDevices import DummyPassive, DummyGround, DummyVoltageSource, DummyVoltageSink, DummyDigitalSource, \ + DummyDigitalSink, DummyAnalogSource, DummyAnalogSink from .DummyDevices import ForcedVoltageCurrentDraw, ForcedVoltageCurrentLimit, ForcedVoltage, ForcedVoltageCurrent, \ ForcedAnalogVoltage, ForcedAnalogSignal, ForcedDigitalSinkCurrentDraw from .MergedBlocks import MergedVoltageSource, MergedDigitalSource, MergedAnalogSource, MergedSpiController diff --git a/edg/abstract_parts/test_power_circuits.py b/edg/abstract_parts/test_power_circuits.py index 073a16ff1..37496706b 100644 --- a/edg/abstract_parts/test_power_circuits.py +++ b/edg/abstract_parts/test_power_circuits.py @@ -5,23 +5,31 @@ from .PowerCircuits import RampLimiter -class RampLimiterTestTop(Block): +class RampLimiterTestTop(DesignTop): def __init__(self): super().__init__() self.dut = self.Block(RampLimiter()) (self.dummyin, ), _ = self.chain(self.dut.pwr_in, self.Block(DummyVoltageSource(voltage_out=12*Volt(tol=0)))) (self.dummyout, ), _ = self.chain(self.dut.pwr_out, self.Block(DummyVoltageSink(current_draw=1*Amp(tol=0)))) + (self.dummyctl, ), _ = self.chain(self.dut.control, self.Block(DummyDigitalSource(voltage_out=3.3*Volt(tol=0)))) (self.dummygnd, ), _ = self.chain(self.dut.gnd, self.Block(DummyGround())) - -class RampLimiterTest(unittest.TestCase): - def test_ramp_limiter(self) -> None: - compiled = ScalaCompiler.compile(RampLimiterTestTop, refinements=Refinements( + def refinements(self) -> Refinements: + return Refinements( class_refinements=[ (Resistor, GenericChipResistor), (Capacitor, GenericMlcc), (Fet, CustomFet), (SwitchFet, CustomFet), + ], instance_values=[ + (['dut', 'drv', 'footprint_spec'], 'Package_TO_SOT_SMD:SOT-23'), + (['dut', 'ctl_fet', 'footprint_spec'], 'Package_TO_SOT_SMD:SOT-23'), + (['dut', 'drv', 'actual_gate_drive'], Range(1.0, 12)), ] - )) + ) + + +class RampLimiterTest(unittest.TestCase): + def test_ramp_limiter(self) -> None: + compiled = ScalaCompiler.compile(RampLimiterTestTop) From 961464c8942bf4507ad72403200c2d2beb975308 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Tue, 2 Sep 2025 01:21:32 -0700 Subject: [PATCH 10/13] Update PowerCircuits.py --- edg/abstract_parts/PowerCircuits.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/edg/abstract_parts/PowerCircuits.py b/edg/abstract_parts/PowerCircuits.py index d06b3b4f3..7518e8030 100644 --- a/edg/abstract_parts/PowerCircuits.py +++ b/edg/abstract_parts/PowerCircuits.py @@ -134,6 +134,9 @@ class RampLimiter(KiCadSchematicBlock): A capacitive divider with Cgs will be generated so the target initial Vgs at less than half the FET Vgs,th (targeting half Vgs,th at Vin,max). + TODO: is this right? + + TODO: allow control to be optional, eliminating the NMOS with a short HOW THIS WORKS: When the input voltage rises, the capacitive divider of Cgs, Cgd brings the gate to a subthreshold voltage. From ae8cfa7f1f4ccf63441b5dbc6c318f458a03d243 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Tue, 2 Sep 2025 20:57:51 -0700 Subject: [PATCH 11/13] Use SwitchFet to model I2Rdson --- edg/abstract_parts/PowerCircuits.py | 3 +-- edg/abstract_parts/test_power_circuits.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/edg/abstract_parts/PowerCircuits.py b/edg/abstract_parts/PowerCircuits.py index 7518e8030..5592a346d 100644 --- a/edg/abstract_parts/PowerCircuits.py +++ b/edg/abstract_parts/PowerCircuits.py @@ -134,7 +134,6 @@ class RampLimiter(KiCadSchematicBlock): A capacitive divider with Cgs will be generated so the target initial Vgs at less than half the FET Vgs,th (targeting half Vgs,th at Vin,max). - TODO: is this right? TODO: allow control to be optional, eliminating the NMOS with a short @@ -180,7 +179,7 @@ def contents(self): super().contents() pwr_voltage = self.pwr_in.link().voltage - self.drv = self.Block(Fet.PFet( + self.drv = self.Block(SwitchFet.PFet( drain_voltage=pwr_voltage, drain_current=self.pwr_out.link().current_drawn, gate_voltage=(0 * Volt(tol=0)).hull(self.target_vgs.upper()), diff --git a/edg/abstract_parts/test_power_circuits.py b/edg/abstract_parts/test_power_circuits.py index 37496706b..3f98bd2be 100644 --- a/edg/abstract_parts/test_power_circuits.py +++ b/edg/abstract_parts/test_power_circuits.py @@ -32,4 +32,4 @@ def refinements(self) -> Refinements: class RampLimiterTest(unittest.TestCase): def test_ramp_limiter(self) -> None: - compiled = ScalaCompiler.compile(RampLimiterTestTop) + ScalaCompiler.compile(RampLimiterTestTop) From c0959b3e76ef7a9cd71eb7bfc864bdbba99ab5ba Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Tue, 2 Sep 2025 23:45:31 -0700 Subject: [PATCH 12/13] Update __init__.py --- edg/abstract_parts/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edg/abstract_parts/__init__.py b/edg/abstract_parts/__init__.py index 7b84ca00d..ee9820b07 100644 --- a/edg/abstract_parts/__init__.py +++ b/edg/abstract_parts/__init__.py @@ -72,7 +72,7 @@ from .AbstractPowerConverters import BuckConverter, DiscreteBuckConverter, BoostConverter, DiscreteBoostConverter from .AbstractPowerConverters import BuckConverterPowerPath, BoostConverterPowerPath, BuckBoostConverterPowerPath from .PowerCircuits import HalfBridge, FetHalfBridge, HalfBridgeIndependent, HalfBridgePwm, FetHalfBridgeIndependent,\ - FetHalfBridgePwmReset + FetHalfBridgePwmReset, RampLimiter from .AbstractLedDriver import LedDriver, LedDriverPwm, LedDriverSwitchingConverter from .AbstractFuse import Fuse, SeriesPowerFuse, PptcFuse, FuseStandardFootprint, TableFuse, SeriesPowerPptcFuse from .AbstractCrystal import Crystal, TableCrystal, OscillatorReference, CeramicResonator From 516a8d67af87caab135a7d9086bfeac756405df2 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Wed, 3 Sep 2025 00:21:31 -0700 Subject: [PATCH 13/13] retune defaults --- edg/abstract_parts/AbstractFets.py | 2 +- edg/abstract_parts/PowerCircuits.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/edg/abstract_parts/AbstractFets.py b/edg/abstract_parts/AbstractFets.py index a2f41049b..c41d95f66 100644 --- a/edg/abstract_parts/AbstractFets.py +++ b/edg/abstract_parts/AbstractFets.py @@ -193,7 +193,7 @@ def PFet(*args, **kwargs): @init_in_parent - def __init__(self, *, frequency: RangeLike = 0*Hertz(tol=0), drive_current: RangeLike, **kwargs) -> None: + def __init__(self, *, frequency: RangeLike = 0*Hertz(tol=0), drive_current: RangeLike = Range.all(), **kwargs) -> None: super().__init__(**kwargs) self.frequency = self.ArgParameter(frequency) diff --git a/edg/abstract_parts/PowerCircuits.py b/edg/abstract_parts/PowerCircuits.py index 5592a346d..d99c2afa2 100644 --- a/edg/abstract_parts/PowerCircuits.py +++ b/edg/abstract_parts/PowerCircuits.py @@ -160,8 +160,8 @@ class RampLimiter(KiCadSchematicBlock): """ @init_in_parent def __init__(self, *, cgd: RangeLike = 10*nFarad(tol=0.5), target_ramp: RangeLike = 1000*Volt(tol=0.25), - target_vgs: RangeLike = (2.0, 3.0)*Volt, max_rds: FloatLike = 1*Ohm, - _cdiv_vgs_factor: RangeLike = (0.02, 0.5)): + target_vgs: RangeLike = (4, 10)*Volt, max_rds: FloatLike = 1*Ohm, + _cdiv_vgs_factor: RangeLike = (0.05, 0.75)): super().__init__() self.gnd = self.Port(Ground.empty(), [Common]) @@ -183,9 +183,8 @@ def contents(self): drain_voltage=pwr_voltage, drain_current=self.pwr_out.link().current_drawn, gate_voltage=(0 * Volt(tol=0)).hull(self.target_vgs.upper()), - gate_threshold_voltage=self.target_vgs, - rds_on=(0, self.max_rds), - power=(0, 0) * Watt # TODO size for through current at Rds,on + gate_threshold_voltage=(0 * Volt(tol=0)).hull(self.target_vgs.lower()), + rds_on=(0, self.max_rds) )) self.cap_gd = self.Block(Capacitor(