« home

NMOSFET Amplifier Circuit

Creator: Kreijstal

electronicsanalog circuitsamplifiermosfetnmoscircuit diagramcetz

Diagram illustrating a basic amplifier circuit topology using an N-channel MOSFET (NMOS), concepts or circuits related to the course "Advanced Analog Integrated Circuits and Systems (AAIC)" (#41178, TU Berlin).


NMOSFET Amplifier Circuit

  Download

PNGPDFSVG

  Code

  Typst

nmosfet-amplifier-circuit.typ (835 lines)

#import "@preview/cetz:0.3.4"
#set page(width: auto, height: auto, margin: 8pt)

#let connect-orthogonal(start_anchor, end_anchor, style: "hv", ..styling) = {
  assert(style in ("hv", "vh"), message: "Style must be 'hv' or 'vh'.")
  let corner = if style == "hv" { (start_anchor, "|-", end_anchor) } else { (start_anchor, "-|", end_anchor) }
  cetz.draw.line(start_anchor, corner, end_anchor, ..styling)
}

#let _draw_label(label, label_pos, label_offset, label_anchor, label_size, text_fill: black) = {
  if label != none {
    cetz.draw.content(
      (rel: label_offset, to: label_pos),
      text(size: label_size, fill: text_fill, label),
      anchor: label_anchor,
    )
  }
}

#let _define_anchors(anchors) = {
  for (name, pos) in anchors { cetz.draw.anchor(name, pos) }
}

#let _base_component(
  position,
  name,
  scale: 1.0,
  rotate: 0deg,
  draw_content_func,
  anchor_definitions,
  label: none,
  label_pos: "center",
  label_anchor: "center",
  label_offset: (0, 0),
  label_size: 8pt,
  text_fill: black,
  ..styling,
) = {
  cetz.draw.group(
    name: name,
    ..styling,
    {
      cetz.draw.set-origin(position)
      cetz.draw.scale(scale)
      cetz.draw.rotate(rotate)
      draw_content_func(..styling)
      _define_anchors(anchor_definitions)
      _draw_label(label, label_pos, label_offset, label_anchor, label_size, text_fill: text_fill)
    },
  )
}

#let _transistor(
  is_pmos: false,
  position,
  name,
  label: none,
  label_pos: auto,
  label_anchor: auto,
  label_offset: (0.05, 0.05),
  label_size: 8pt,
  show_pin_labels: false,
  pin_label_size: 7pt,
  show_gate_bubble: auto,
  bubble_radius_factor: 0.08,
  scale: 1.0,
  rotate: 0deg,
  width: 0.9,
  height: 1.2,
  gate_lead_factor: 0.3,
  bulk_lead_factor: 0.3,
  gate_pos_factor: 0.3,
  channel_pos_factor: 0.4,
  gate_v_extent_factor: 0.35,
  channel_v_extent_factor: 0.35,
  thick_factor: 2.0,
  arrow_scale: 0.8,
  arrow_fill: black,
  ..styling,
) = {
  let final_label_pos = if label_pos == auto { if is_pmos { "S" } else { "D" } } else { label_pos }
  let final_label_anchor = if label_anchor == auto { if is_pmos { "south-west" } else { "north-west" } } else {
    label_anchor
  }
  let final_show_gate_bubble = if show_gate_bubble == auto { is_pmos } else { show_gate_bubble }

  cetz.draw.group(
    name: name,
    ..styling,
    {
      cetz.draw.set-origin(position)
      cetz.draw.scale(scale)
      cetz.draw.rotate(rotate)

      let center_y = height / 2
      let gate_line_x = gate_pos_factor * width
      let channel_line_x = channel_pos_factor * width
      let gate_v_extent = height * gate_v_extent_factor
      let channel_v_extent = height * channel_v_extent_factor
      let drain_term_rel = (width, if is_pmos { 0 } else { height })
      let source_term_rel = (width, if is_pmos { height } else { 0 })
      let gate_term_rel = (-gate_lead_factor * width, center_y)
      let bulk_term_rel = (width + bulk_lead_factor * width, center_y)
      let gate_line_top = (gate_line_x, center_y + gate_v_extent)
      let gate_line_bottom = (gate_line_x, center_y - gate_v_extent)
      let gate_conn_pt = (gate_line_x, center_y)
      let channel_top = (channel_line_x, center_y + channel_v_extent)
      let channel_bottom = (channel_line_x, center_y - channel_v_extent)
      let bulk_conn_pt = (channel_line_x, center_y)
      let horiz_top = (width, center_y + channel_v_extent)
      let horiz_bottom = (width, center_y - channel_v_extent)
      let line_thickness = 0.6pt * thick_factor

      _define_anchors((
        ("G", gate_term_rel),
        ("D", drain_term_rel),
        ("S", source_term_rel),
        ("B", bulk_term_rel),
        ("center", (width / 2, height / 2)),
        ("bulk_conn", bulk_conn_pt),
        ("gate_conn", gate_conn_pt),
        ("north", (width / 2, height)),
        ("south", (width / 2, 0)),
        ("east", (width, height / 2)),
        ("west", (0, height / 2)),
        ("north-east", (width, height)),
        ("south-west", (0, 0)),
        ("south-east", (width, 0)),
        ("default", gate_term_rel),
      ))

      cetz.draw.line(channel_bottom, channel_top, ..styling, thickness: line_thickness)
      cetz.draw.line(gate_line_bottom, gate_line_top, ..styling, thickness: line_thickness)
      cetz.draw.line(gate_term_rel, gate_conn_pt, ..styling)
      cetz.draw.line(bulk_conn_pt, bulk_term_rel, ..styling)

      if is_pmos {
        cetz.draw.line(drain_term_rel, horiz_bottom, ..styling)
        cetz.draw.line(horiz_bottom, channel_bottom, ..styling)
        cetz.draw.line(source_term_rel, horiz_top, ..styling)
        cetz.draw.line(horiz_top, channel_top, ..styling, mark: (end: "stealth", fill: arrow_fill, scale: arrow_scale))
        if final_show_gate_bubble {
          let bubble_radius = height * bubble_radius_factor
          cetz.draw.circle((rel: (-bubble_radius, 0), to: "gate_conn"), radius: bubble_radius, ..styling, fill: white)
        }
      } else {
        cetz.draw.line(drain_term_rel, horiz_top, ..styling)
        cetz.draw.line(horiz_top, channel_top, ..styling)
        cetz.draw.line(horiz_bottom, source_term_rel, ..styling)
        cetz.draw.line(
          channel_bottom,
          horiz_bottom,
          ..styling,
          mark: (end: "stealth", fill: arrow_fill, scale: arrow_scale),
        )
      }

      if show_pin_labels {
        cetz.draw.content((rel: (-0.05, 0), to: "G"), text(size: pin_label_size, $G$), anchor: "east")
        cetz.draw.content(
          (rel: (0.05, 0), to: "S"),
          text(size: pin_label_size, $S$),
          anchor: if is_pmos { "west" } else { "south" },
        )
        cetz.draw.content(
          (rel: (0.05, 0), to: "D"),
          text(size: pin_label_size, $D$),
          anchor: if is_pmos { "west" } else { "north" },
        )
        cetz.draw.content((rel: (0.05, 0), to: "B"), text(size: pin_label_size, $B$), anchor: "west")
      }
      _draw_label(label, final_label_pos, label_offset, final_label_anchor, label_size)
    },
  )
}

#let nmos_transistor(..args) = _transistor(is_pmos: false, ..args)
#let pmos_transistor(..args) = _transistor(is_pmos: true, ..args)

#let gnd_symbol(
  position,
  name,
  label: none,
  label_pos: "north",
  label_anchor: "south",
  label_offset: (0, 0.1),
  label_size: 8pt,
  scale: 1.0,
  rotate: 0deg,
  lead_length: 0.3,
  bar_width: 0.5,
  bar_spacing: 0.05,
  bar_width_factors: (1.0, 0.7, 0.4),
  ..styling,
) = {
  assert(bar_width_factors.len() == 3, message: "bar_width_factors must have 3 elements.")
  let draw_func(..styling) = {
    let y_coords = (-lead_length, -lead_length - bar_spacing, -lead_length - 2 * bar_spacing)
    cetz.draw.line((0, 0), (0, -lead_length), ..styling)
    for (idx, y) in y_coords.enumerate() {
      let half_w = bar_width * bar_width_factors.at(idx) / 2
      cetz.draw.line((-half_w, y), (half_w, y), ..styling)
    }
  }
  let south_y = -lead_length - 2 * bar_spacing
  let anchors = (
    ("T", (0, 0)),
    ("north", (0, 0)),
    ("south", (0, south_y)),
    ("west", (-bar_width * bar_width_factors.at(0) / 2, -lead_length)),
    ("east", (bar_width * bar_width_factors.at(0) / 2, -lead_length)),
    ("center", (0, (-lead_length + south_y) / 2)),
    ("default", (0, 0)),
  )
  _base_component(
    position,
    name,
    scale: scale,
    rotate: rotate,
    draw_func,
    anchors,
    label: label,
    label_pos: label_pos,
    label_anchor: label_anchor,
    label_offset: label_offset,
    label_size: label_size,
    ..styling,
  )
}

#let resistor(
  position,
  name,
  label: none,
  label_pos: "south",
  label_anchor: "north",
  label_offset: (0, -0.1),
  label_size: 8pt,
  scale: 1.0,
  rotate: 0deg,
  width: 0.8,
  height: 0.3,
  zigs: 3,
  lead_extension: 0.3,
  ..styling,
) = {
  let draw_func(..styling) = {
    let hw = width / 2
    let hh = height / 2
    let lead_start_x = -hw - lead_extension
    let lead_end_x = hw + lead_extension
    let zig_start_x = -hw
    let num_segments = zigs * 2
    let seg_h = width / num_segments
    let sgn = 1
    cetz.draw.line(
      (lead_start_x, 0),
      (zig_start_x, 0),
      (rel: (seg_h / 2, hh * sgn)),
      ..for _ in range(num_segments - 1) {
        sgn *= -1
        ((rel: (seg_h, hh * 2 * sgn)),)
      },
      (rel: (seg_h / 2, hh)),
      (lead_end_x, 0),
      ..styling,
    )
  }
  let hw = width / 2
  let hh = height / 2
  let lead_start_x = -hw - lead_extension
  let lead_end_x = hw + lead_extension
  let anchors = (
    ("L", (lead_start_x, 0)),
    ("R", (lead_end_x, 0)),
    ("center", (0, 0)),
    ("north", (0, hh)),
    ("south", (0, -hh)),
    ("east", (lead_end_x, 0)),
    ("west", (lead_start_x, 0)),
    ("T", (0, hh)),
    ("B", (0, -hh)),
    ("default", (lead_start_x, 0)),
  )
  _base_component(
    position,
    name,
    scale: scale,
    rotate: rotate,
    draw_func,
    anchors,
    label: label,
    label_pos: label_pos,
    label_anchor: label_anchor,
    label_offset: label_offset,
    label_size: label_size,
    ..styling,
  )
}

#let capacitor(
  position,
  name,
  label: none,
  label_pos: "south",
  label_anchor: "north",
  label_offset: (0, -0.1),
  label_size: 8pt,
  scale: 1.0,
  rotate: 0deg,
  plate_height: 0.6,
  plate_gap: 0.2,
  lead_extension: 0.5,
  ..styling,
) = {
  let draw_func(..styling) = {
    let hg = plate_gap / 2
    let hh = plate_height / 2
    let lead_start_x = -hg - lead_extension
    let lead_end_x = hg + lead_extension
    let plate_left_x = -hg
    let plate_right_x = hg
    cetz.draw.line((lead_start_x, 0), (plate_left_x, 0), ..styling)
    cetz.draw.line((plate_left_x, -hh), (plate_left_x, hh), ..styling)
    cetz.draw.line((plate_right_x, hh), (plate_right_x, -hh), ..styling)
    cetz.draw.line((plate_right_x, 0), (lead_end_x, 0), ..styling)
  }
  let hg = plate_gap / 2
  let hh = plate_height / 2
  let lead_start_x = -hg - lead_extension
  let lead_end_x = hg + lead_extension
  let anchors = (
    ("L", (lead_start_x, 0)),
    ("R", (lead_end_x, 0)),
    ("center", (0, 0)),
    ("north", (0, hh)),
    ("south", (0, -hh)),
    ("east", (lead_end_x, 0)),
    ("west", (lead_start_x, 0)),
    ("T", (0, hh)),
    ("B", (0, -hh)),
    ("default", (lead_start_x, 0)),
  )
  _base_component(
    position,
    name,
    scale: scale,
    rotate: rotate,
    draw_func,
    anchors,
    label: label,
    label_pos: label_pos,
    label_anchor: label_anchor,
    label_offset: label_offset,
    label_size: label_size,
    ..styling,
  )
}

#let voltage_source(
  position,
  name,
  label: none,
  annotation_label_pos: "left",
  annotation_label_anchor: auto,
  annotation_label_offset: auto,
  annotation_label_size: 8pt,
  show_voltage_annotation: true,
  voltage_arrow_pos: "left",
  voltage_arrow_dir: "down",
  voltage_arrow_length_factor: 2,
  voltage_arrow_offset_factor: 0.7,
  arrow_scale: 1.0,
  arrow_fill: black,
  stroke_thickness: 0.6pt,
  scale: 1.0,
  rotate: 0deg,
  radius: 0.3,
  lead_length: 0.3,
  ..styling,
) = {
  let draw_func(..styling) = {
    let top_y = radius
    let bottom_y = -radius
    let top_lead_y = top_y + lead_length
    let bottom_lead_y = bottom_y - lead_length
    cetz.draw.circle((0, 0), radius: radius, ..styling)
    cetz.draw.line((0, top_y), (0, top_lead_y), ..styling)
    cetz.draw.line((0, bottom_y), (0, bottom_lead_y), ..styling)
    if show_voltage_annotation {
      let arrow_x = if voltage_arrow_pos == "left" { -radius * (1 + voltage_arrow_offset_factor) } else {
        radius * (1 + voltage_arrow_offset_factor)
      }
      let arrow_len = radius * voltage_arrow_length_factor
      let arrow_half_len = arrow_len / 2
      let (arrow_start_y, arrow_end_y) = if voltage_arrow_dir == "down" { (arrow_half_len, -arrow_half_len) } else {
        (-arrow_half_len, arrow_half_len)
      }
      cetz.draw.line(
        (arrow_x, arrow_start_y),
        (arrow_x, arrow_end_y),
        ..styling,
        mark: (
          end: "stealth",
          scale: arrow_scale * 0.4,
          fill: arrow_fill,
          stroke: (paint: black, thickness: stroke_thickness),
        ),
      )
      if label != none {
        let (default_anchor, default_offset) = if annotation_label_pos == "left" { ("east", (-0.05, 0)) } else {
          ("west", (0.05, 0))
        }
        let final_anchor = if annotation_label_anchor == auto { default_anchor } else { annotation_label_anchor }
        let final_offset = if annotation_label_offset == auto { default_offset } else { annotation_label_offset }
        cetz.draw.content(
          (rel: (0.1 * scale * arrow_x / calc.abs(arrow_x), 0), to: (arrow_x, 0)),
          text(size: annotation_label_size, fill: arrow_fill, label),
          anchor: final_anchor,
          offset: final_offset,
        )
      }
    }
  }
  let top_lead_y = radius + lead_length
  let bottom_lead_y = -radius - lead_length
  let anchors = (
    ("T", (0, top_lead_y)),
    ("B", (0, bottom_lead_y)),
    ("center", (0, 0)),
    ("north", (0, top_lead_y)),
    ("south", (0, bottom_lead_y)),
    ("east", (radius, 0)),
    ("west", (-radius, 0)),
    ("default", (0, top_lead_y)),
  )
  _base_component(
    position,
    name,
    scale: scale,
    rotate: rotate,
    draw_func,
    anchors,
    label: none,
    ..styling,
  )
}

#let node(
  position,
  name,
  label: none,
  radius: 0.05,
  label_size: 8pt,
  label_offset: (0, 0),
  label_anchor: "center",
  fill: white,
  text_fill: black,
  scale: 1.0,
  rotate: 0deg,
  ..styling,
) = {
  let draw_func(..styling) = {
    cetz.draw.circle((0, 0), radius: radius, ..styling, fill: fill)
  }
  let diag_offset = radius * calc.cos(45deg)
  let anchors = (
    ("center", (0, 0)),
    ("default", (0, 0)),
    ("north", (0, radius)),
    ("south", (0, -radius)),
    ("east", (radius, 0)),
    ("west", (-radius, 0)),
    ("north-east", (diag_offset, diag_offset)),
    ("north-west", (-diag_offset, diag_offset)),
    ("south-east", (diag_offset, -diag_offset)),
    ("south-west", (-diag_offset, -diag_offset)),
  )
  _base_component(
    position,
    name,
    scale: scale,
    rotate: rotate,
    draw_func,
    anchors,
    label: label,
    label_pos: "center",
    label_anchor: label_anchor,
    label_offset: label_offset,
    label_size: label_size,
    text_fill: text_fill,
    ..styling,
  )
}

#let vdd_symbol(
  position,
  name,
  label: none,
  label_pos: "north",
  label_anchor: "south",
  label_offset: (0, 0.1),
  label_size: 8pt,
  scale: 1.0,
  rotate: 0deg,
  stem_length: 0.3,
  bar_width: 0.5,
  text_fill: black,
  ..styling,
) = {
  let draw_func(..styling) = {
    let stem_top_y = stem_length
    let bar_half_width = bar_width / 2
    cetz.draw.line((0, 0), (0, stem_top_y), ..styling)
    cetz.draw.line((-bar_half_width, stem_top_y), (bar_half_width, stem_top_y), ..styling)
  }
  let stem_top_y = stem_length
  let bar_half_width = bar_width / 2
  let anchors = (
    ("B", (0, 0)),
    ("south", (0, 0)),
    ("default", (0, 0)),
    ("T", (0, stem_top_y)),
    ("north", (0, stem_top_y)),
    ("TL", (-bar_half_width, stem_top_y)),
    ("TR", (bar_half_width, stem_top_y)),
    ("west", (-bar_half_width, stem_top_y)),
    ("east", (bar_half_width, stem_top_y)),
    ("center", (0, stem_top_y / 2)),
  )
  _base_component(
    position,
    name,
    scale: scale,
    rotate: rotate,
    draw_func,
    anchors,
    label: label,
    label_pos: label_pos,
    label_anchor: label_anchor,
    label_offset: label_offset,
    label_size: label_size,
    text_fill: text_fill,
    ..styling,
  )
}

#let current_source(
  position,
  name,
  label: none,
  label_pos: "west",
  label_anchor: "west",
  label_offset: (0.1, 0),
  label_size: 8pt,
  scale: 1.0,
  rotate: 0deg,
  radius: 0.3,
  lead_length: 0.3,
  arrow_dir: "up",
  arrow_scale: 1.0,
  arrow_fill: black,
  ..styling,
) = {
  let draw_func(..styling) = {
    let top_y = radius
    let bottom_y = -radius
    let top_lead_y = top_y + lead_length
    let bottom_lead_y = bottom_y - lead_length
    cetz.draw.circle((0, 0), radius: radius, ..styling)
    cetz.draw.line((0, top_y), (0, top_lead_y), ..styling)
    cetz.draw.line((0, bottom_y), (0, bottom_lead_y), ..styling)
    let arrow_v_extent = radius * 0.7
    assert(arrow_dir in ("up", "down"), message: "Arrow direction must be 'up' or 'down'.")
    let (arrow_start_y, arrow_end_y) = if arrow_dir == "up" { (-arrow_v_extent, arrow_v_extent) } else {
      (arrow_v_extent, -arrow_v_extent)
    }
    cetz.draw.line(
      (0, arrow_start_y),
      (0, arrow_end_y),
      ..styling,
      mark: (end: "stealth", scale: arrow_scale * 0.4, fill: arrow_fill),
    )
  }
  let top_lead_y = radius + lead_length
  let bottom_lead_y = -radius - lead_length
  let anchors = (
    ("T", (0, top_lead_y)),
    ("B", (0, bottom_lead_y)),
    ("center", (0, 0)),
    ("north", (0, top_lead_y)),
    ("south", (0, bottom_lead_y)),
    ("east", (radius, 0)),
    ("west", (-radius, 0)),
    ("default", (0, top_lead_y)),
  )
  _base_component(
    position,
    name,
    scale: scale,
    rotate: rotate,
    draw_func,
    anchors,
    label: label,
    label_pos: label_pos,
    label_anchor: label_anchor,
    label_offset: label_offset,
    label_size: label_size,
    ..styling,
  )
}

#let voltage_points(
  position,
  name,
  label: none,
  annotation_label_pos: "left",
  annotation_label_anchor: auto,
  annotation_label_offset: auto,
  annotation_label_size: 8pt,
  annotation_text_fill: black,
  show_voltage_annotation: true,
  voltage_arrow_pos: "left",
  voltage_arrow_dir: "down",
  arrow_length_factor: 1.0,
  arrow_offset: 0.3,
  arrow_scale: 1.0,
  arrow_fill: black,
  arrow_stroke: black,
  arrow_stroke_thickness: 0.6pt,
  point_separation: 0.6,
  point_radius: 0.05,
  point_fill: black,
  point_stroke: none,
  show_point_labels: false,
  top_label: $[+]$,
  bottom_label: $[-]$,
  point_label_size: 7pt,
  point_text_fill: black,
  top_label_offset: (0, 0.05),
  top_label_anchor: "south",
  bottom_label_offset: (0, -0.05),
  bottom_label_anchor: "north",
  scale: 1.0,
  rotate: 0deg,
  ..styling,
) = {
  let draw_func(..styling) = {
    let half_sep = point_separation / 2
    cetz.draw.circle((0, half_sep), radius: point_radius, fill: point_fill, stroke: point_stroke, ..styling)
    cetz.draw.circle((0, -half_sep), radius: point_radius, fill: point_fill, stroke: point_stroke, ..styling)
    if show_voltage_annotation {
      let arrow_x = if voltage_arrow_pos == "left" { -arrow_offset } else { arrow_offset }
      let arrow_len = point_separation * arrow_length_factor
      let arrow_half_len = arrow_len / 2
      let (arrow_start_y, arrow_end_y) = if voltage_arrow_dir == "down" { (arrow_half_len, -arrow_half_len) } else {
        (-arrow_half_len, arrow_half_len)
      }
      cetz.draw.line(
        (arrow_x, arrow_start_y),
        (arrow_x, arrow_end_y),
        stroke: (paint: arrow_stroke, thickness: arrow_stroke_thickness),
        ..styling,
        mark: (
          end: "stealth",
          scale: arrow_scale * 0.4,
          fill: arrow_fill,
          stroke: (paint: arrow_stroke, thickness: arrow_stroke_thickness),
        ),
      )
      if label != none {
        let (default_anchor, default_offset) = if annotation_label_pos == "left" { ("east", (-0.05, 0)) } else {
          ("west", (0.05, 0))
        }
        let final_anchor = if annotation_label_anchor == auto { default_anchor } else { annotation_label_anchor }
        let final_offset = if annotation_label_offset == auto { default_offset } else { annotation_label_offset }
        cetz.draw.content(
          (rel: (0.1 * scale * arrow_x / calc.abs(arrow_x), 0), to: (arrow_x, 0)),
          text(size: annotation_label_size, fill: annotation_text_fill, label),
          anchor: final_anchor,
          offset: final_offset,
        )
      }
    }
    if show_point_labels {
      _draw_label(top_label, "T", top_label_offset, top_label_anchor, point_label_size, text_fill: point_text_fill)
      _draw_label(
        bottom_label,
        "B",
        bottom_label_offset,
        bottom_label_anchor,
        point_label_size,
        text_fill: point_text_fill,
      )
    }
  }
  let half_sep = point_separation / 2
  let anchors = (
    ("T", (0, half_sep)),
    ("B", (0, -half_sep)),
    ("center", (0, 0)),
    ("north", (0, half_sep)),
    ("south", (0, -half_sep)),
    ("east", (point_radius, 0)),
    ("west", (-point_radius, 0)),
    ("default", (0, half_sep)),
  )
  _base_component(
    position,
    name,
    scale: scale,
    rotate: rotate,
    draw_func,
    anchors,
    label: none,
    ..styling,
  )
}

#let wire_hop(
  wire1_start,
  wire1_end,
  wire2_start,
  wire2_end,
  hopping_wire: 1,
  hop_radius: 0.15,
  hop_direction: 1,
  ..styling,
) = {
  assert(hopping_wire in (1, 2), message: "hopping_wire must be 1 or 2.")
  assert(hop_direction in (1, -1), message: "hop_direction must be 1 or -1.")
  let intersection = cetz.intersection.line-line(wire1_start, wire1_end, wire2_start, wire2_end)
  assert(intersection != none, message: "Wires do not intersect, cannot hop.")
  let (straight_start, straight_end, hopping_start, hopping_end) = if hopping_wire == 1 {
    (wire2_start, wire2_end, wire1_start, wire1_end)
  } else { (wire1_start, wire1_end, wire2_start, wire2_end) }
  cetz.draw.line(straight_start, straight_end, ..styling)
  let hop_vec = cetz.vector.sub(hopping_end, hopping_start)
  let wire_angle = calc.atan2(..hop_vec)
  let hop_unit_vec = cetz.vector.norm(hop_vec)
  assert(hop_unit_vec != none, message: "Cannot get unit vector for zero-length hopping wire.")
  let offset_vec_neg = cetz.vector.scale(hop_unit_vec, -hop_radius)
  let offset_vec_pos = cetz.vector.scale(hop_unit_vec, hop_radius)
  let arc_start_point = (rel: offset_vec_neg, to: intersection)
  let arc_end_point = (rel: offset_vec_pos, to: intersection)
  let arc_start_angle = wire_angle
  let arc_stop_angle = wire_angle + (180deg * hop_direction)
  cetz.draw.line(hopping_start, arc_start_point, ..styling)
  cetz.draw.arc(
    (
      rel: cetz.vector.scale((calc.cos(arc_start_angle), calc.sin(arc_start_angle)), hop_radius * 2),
      to: arc_start_point,
    ),
    start: arc_start_angle,
    stop: arc_stop_angle,
    radius: hop_radius,
    ..styling,
  )
  cetz.draw.line(arc_end_point, hopping_end, ..styling)
}

#cetz.canvas({
  let default_stroke = (stroke: (thickness: .6pt))
  // Coordinates
  let x_vin = 0
  let x_r1 = 1.0
  let x_m1 = 1.9
  let x_out = x_m1 + 0.9
  let x_cl = x_out + 2.0
  let x_vout = x_cl + 1.0
  let y_gate = 1.6
  let y_m1_base = 1.0
  let y_m1_s = y_m1_base
  let y_m1_d = y_m1_base + 1.2
  let y_m1_b = y_m1_base + 0.6
  let y_vdd = y_m1_d + 1.5
  let y_gnd = -1.0
  let y_cl_center = y_m1_s - 0.95

  // Components
  voltage_source((x_vin, y_gate - 1), "Vin", label: $V_"in"$, radius: 0.4, lead_length: 0.2, ..default_stroke)
  resistor((x_r1, y_gate), "R1", label: $R_1$, label_pos: "north", label_offset: (0, 0.4), width: 1.0, ..default_stroke)
  nmos_transistor(
    (x_m1, y_m1_base),
    "M1",
    label: $M_1$,
    label_pos: "east",
    label_anchor: "west",
    label_offset: (0.3, 0.3),
    ..default_stroke,
  )
  vdd_symbol((x_out, y_vdd), "Vdd", label: $V_"DD"$, ..default_stroke)
  resistor(
    (x_out, y_m1_s - 1),
    "R2",
    rotate: 90deg,
    label: $R_2$,
    label_pos: "west",
    label_offset: (0.5, 0.5),
    ..default_stroke,
  )
  capacitor(
    (x_cl, y_cl_center),
    "CL",
    rotate: 90deg,
    label: $C_L$,
    label_pos: "east",
    label_offset: (-0.2, -0.5),
    ..default_stroke,
  )
  node((x_vout, y_m1_b), "VoutNode", label: $V_"out"$, label_offset: (0.15, 0), label_anchor: "west", ..default_stroke)

  // Ground connections
  for (name, pos) in (
    ("GND_Vin", (x_vin, y_gnd)),
    ("GND_M1B", (x_m1 + 1.27, y_m1_b)),
    ("GND_R2", (x_out, y_gnd)),
    ("GND_CL", (x_cl, y_gnd)),
  ) {
    gnd_symbol(pos, name, ..default_stroke)
  }

  // Connections
  connect-orthogonal("Vin.T", "R1.L", style: "hv", ..default_stroke)
  connect-orthogonal("Vin.B", "GND_Vin.T", style: "hv", ..default_stroke)
  connect-orthogonal("R1.R", "M1.G", style: "hv", ..default_stroke)
  connect-orthogonal("M1.D", "Vdd.B", style: "hv", ..default_stroke)
  connect-orthogonal("M1.B", "GND_M1B.T", style: "hv", ..default_stroke)
  connect-orthogonal("M1.S", "R2.R", style: "hv", ..default_stroke)
  connect-orthogonal("GND_R2.T", "R2.L", style: "hv", ..default_stroke)
  connect-orthogonal("GND_CL.T", "CL.L", style: "hv", ..default_stroke)
  connect-orthogonal("CL.R", "M1.S", style: "hv", ..default_stroke)
  connect-orthogonal("VoutNode", "M1.S", style: "hv", ..default_stroke)
})