Quickstart: Your First JITX Design

NOTE: Before starting this tutorial, you should have installed and set up JITX.

Introduction

We'll go from zero JITX experience to a ready-to-order printed circuit board assembly (PCBA) design.

To do that, we'll design a parametric power regulator circuit:

  1. Create a new project
  2. Define our design in code
  3. Run that code to generate a schematic and board

JITX Workflow - Idea to Manufacturing

The following is the end-to-end workflow of going from an idea to a design using JITX.

  1. Define requirements (Conventional EE process).
  2. System level design / block diagram (Conventional EE process).
  3. Create a new JITX project.
  4. Pull in major components to your JITX project.
    1. Import your components from an existing design or component file (KiCad or Altium).
    2. Search OCDB to see if the part exists.
  5. Connectivity + Passives
    • use net statements to connect your components together
    • use res-strap, cap-strap, etc. to create passives, pullups, etc.
  6. Checks
    • write checks for your components and design, then run them to verify validity of your design
  7. Export to CAD (Altium or KiCad)
  8. Layout, Order Board (Conventional EE process)

Tutorial - Design a PCB in JITX

1. Create Project

In the JITX VS Code panel, select "New project".

Fresh JITX Install

Fresh JITX Install

Create a new directory with a descriptive name like regulator-tutorial. This launches a new VS Code window in the new project directory, which is populated with a starter design (main.stanza), and the most recent version of the JITX components library, OCDB. Click the Explorer icon in the top left corner of VS Code...

Explorer Icon

...to open and view the files you just created. Take a look at main.stanza.

New Empty JITX Project

New JITX Project Created

TIP: main.stanza is the main file from which you run your design.

2. Define a Board

In the new VS Code window, open main.stanza.

In this file, the design is contained in a pcb-module named my-design. A pcb-module is a construct you'll work with a lot - they can be generated directly into a schematic/board, or used as a subsystem of another board.

This module includes includes some pins, a single resistor, and a version label placed on the bottom silkscreen layer:

pcb-module my-design :
  ; define some pins/ports
  pin gnd
  pin power-5v
  pin signal

  ; Create a 10k resistor component
  res-strap(power-5v, signal, 10.0e3)

  inst version-label  : ocdb/artwork/board-text/version-silkscreen("Version 0.0")
  place(version-label) at loc(0.0, height(board-shape) / 2.0 - 1.0) on Bottom

Near the bottom of main.stanza contains the line:

set-main-module(my-design)

Which specifies the my-design pcb-module as the top level design and generates that module as a board.

Then we call the following, which launches a visualization of the schematic, board, and design-explorer:

; View the results
view-board()
view-schematic()
view-design-explorer()

3. Generate a Board

With an active cursor in main.stanza, press Ctrl + Enter to run the program. This compiles our design, thus generating the board that we just defined above.

Every time we press Ctrl + Enter, we reload this design (and any updates we made to the library code). So we could edit the version text or location of the label, press Ctrl + Enter, and see the newly generated schematic and board.

JITX Project Compiled

New JITX Project Compiled

NOTE: Your schematic might initially look like a blank screen like below. Be sure to zoom in to the bottom left corner to see the design.

JITX Project Compiled

4. Create a Power Regulator Circuit

In JITX we use pcb-module to create reusable circuits. Let's create one for a parametric power regulator based on the commodity LM317A from Unisonic.

Here's the application circuit from the datasheet:

LM317

First, let's remove the reference to the pins and the resistor (created with res-strap) from the my-design module so we don't clutter our design.

Now let's copy the JITX code below that implements our application circuit into our design main.stanza above the my-design block:

NOTE: Check out the finished code if you're wondering where to copy/paste functions.

pcb-module lm317a-regulator (output-voltage:Toleranced) :
  pin vin
  pin vout
  pin gnd
  pin adj

  inst lm317a : ocdb/components/unisonic/LM317A/component
  net (vin  lm317a.input)
  net (vout lm317a.output)
  
  cap-strap(lm317a.input, gnd, 0.1e-6)
  cap-strap(lm317a.output, gnd, 1.0e-6)
 
  inst output-voltage-divider : ocdb/modules/passive-circuits/voltage-divider(source-voltage = in-voltage, divider-output = adj-voltage, current = current) where :
    val in-voltage = output-voltage
    val adj-voltage = tol%(1.25, 15.0)
    val current = 5.0e-3

  net (output-voltage-divider.in lm317a.output) 
  net (output-voltage-divider.out lm317a.adj adj) 
  net (output-voltage-divider.lo gnd)

This LM317A accepts a range of values for its output voltage by using a JITX type called Toleranced. Toleranced allows us to construct, query and check ranges flexibly. A more detailed explanation of Toleranced is available here.

Finally, add the regulator circuit to my-design by inserting an inst statement right after the header pcb-module my-design (remember to indent this line so it's part of the my-design pcb-module block):

  val target-voltage = tol%(3.3, 10.0)
  inst reg : lm317a-regulator(target-voltage)

Notice the use of tol% as our argument to lm317a-regulator. This is used to specify that the output voltage is within a +/- 10% range of the typical (typ) value. Toleranced values are designed to make value ranges easy. Some functions take Toleranced values directly as an argument type. Others take Doubles, and in that case we can extract a Double from our Toleranced value using helper functions like tol+% and typ.

Press Ctrl + Enter to reload the program and generate a new design.

We can change the voltage we want out of the regulator and the circuit will update automatically, choosing new resistors to create the correct output voltage. This is a powerful paradigm - instead of redesigning the entire subcircuit whenever change a parameter, we write one circuit generator, one time, and then it can be re-used many times. That's some of the power of software-defined hardware!

LM317

12.0V LM317A regulator generated in JITX

5. Export Design to CAD

We're now ready to export our design:

Altium
  • Open Altium and install the Altium JITX plugin. Our export process requires Altium to be open. It doesn't have to be open to anything in particular.
Kicad
  • Go into the file helpers.stanza and scroll down to the export-to-cad() function. Change the argument that says `altium to say `kicad.

Go back to main.stanza and do Ctrl + Enter again to compile the design. Now, in the JITX Shell, run export-design() to generate CAD files and our bill of materials.

These files will be generated in the location specified by our call to set-current-design (by default, this is the "./designs/jitx-design" directory). You'll have now generated either an Alitum or KiCad project. Open your CAD software of choice, open the generated project (in the "./designs/jitx-design" folder), and perform the layout just as you normally would.

We've just created our first software-defined electrical design, ready for placement and routing in CAD. Welcome to JITX.

Explanation - How It All Works

We've now designed a working PCB end-to-end. Let's now take a closer look at what we actually did, the constructs we used, and how they work.

The below represents the minimum we'll need to grasp - if we can understand this implementation of the LM317A, we know enough of the JITX language to be productive.

Modules

JITX modules are circuits, defined by pcb-module. They can be large circuits (e.g. an entire PCB) or much smaller subcircuits (e.g. a single LED and ballast resistor). They all consist of a collection of components and other modules with electrical connections, alongside data and logic that defines their input/output and behavior.

We started with the default main.stanza file which contained a default pcb-module called my-design. This (and any other) pcb-module represents a PCB or subcircuit of a PCB. When we called set-main-module(my-design), we set my-design as the top level module that will be compiled as a PCB.

Then, we made our own module by defining a pcb-module named lm317a-regulator. This module accepts output-voltage as an argument. output-voltage is specified to be of type Double. We use types in JITX to make it easier to write correct code. (e.g. 10.0 is a Double. 10 would be an Int).

pcb-module lm317a-regulator (output-voltage:Double) :

This lm317a-regulator module is not a full PCB - it serves as a subcircuit which becomes part of the larger my-design module. This is a powerful concept - it means we can create subcircuits that are reusable across designs, that are defined hierarchically, that can modify themselves based on our arguments, and much more.

Components

Components are defined with pcb-component. These define the lowest level, atomic components. They're a lot like modules, but they contain a landpattern and they cannot instantiate other components or modules.

We didn't actually define any of our own components in this tutorial, but we instantiated one here:

  inst lm317a : ocdb/components/unisonic/LM317A/component

You can keep working through the Quickstarts now, but feel free to checkout Create a Component to learn more about creating and defining components.

Ports

Connections to a module (a circuit) are expressed as ports (or, equivalently, pins). In our lm317a-regulator module above, we setup the connectivity of our circuit by creating four pins named vin, vout, gnd and adj. These are logical ports (not physical pins) that allow external connections to the circuit inside the pcb-module. These connections are necessary when the module is included in a higher-level design.

  pin vin
  pin vout
  pin gnd
  pin adj

We then added the LM317A pcb-component to the lm317a-module module. We did so using an inst statement:

  inst lm317a : ocdb/components/unisonic/LM317A/component

The above line translates to "add component to my design, call it lm317a, and go find its definition in "ocdb/components/unisonic/LM317A/component".

We now have the LM317A component instantiated, and we can connect to it by connecting to its exposed pins. We connect (just like drawing a wire in a schematic) our local port vin to the LM317A pin named input, and our local port vout to the LM317A pin named output.

  inst lm317a : ocdb/components/unisonic/LM317A/component
  net (vin  lm317a.input)
  net (vout lm317a.output)

Circuit Generators

In JITX, it's common to add circuits to a design with a single function call. We can add capacitors using the cap-strap convenience function (import ocdb/utils/generic-components to use this function). These lines add an 0.1uF capacitor to the input, and a 1.0uF capacitor to the output. These calls to cap-strap choose real capacitors (selected based on optimization goals), and connect them to our circuit.

  cap-strap(lm317a.input, gnd, 0.1e-6)
  cap-strap(lm317a.output, gnd, 1.0e-6)

Then, we use inst to add a voltage divider that sets the output voltage. voltage-divider is a parametric function that generates and returns a pcb-module subcircuit. We can specify the input voltage, target output voltage, resistor tolerance, and divider current. This module will find a set of resistors that produces the correct output voltage given the input constraints, ensuring that all resistor values are common and triply-sourceable.

Finally, a set of net statements connects the voltage divider to the LM317A and to the gnd port.

  inst output-voltage-divider : ocdb/modules/passive-circuits/voltage-divider(source-voltage = in-voltage, divider-output = adj-voltage, current = current) where :
    val in-voltage = output-voltage
    val adj-voltage = tol%(1.25, 15.0)
    val current = 5.0e-3

  net (output-voltage-divider.in lm317a.output) 
  net (output-voltage-divider.out lm317a.adj adj) 
  net (output-voltage-divider.lo gnd)

Next Step

Continue to the Quickstart : Organize a Schematic.