NOTE: Before starting this tutorial, you should have installed and set up JITX.
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:
- Create a new project
- Define our design in code
- Run that code to generate a schematic and board
The following is the end-to-end workflow of going from an idea to a design using JITX.
- Define requirements (Conventional EE process).
- System level design / block diagram (Conventional EE process).
- Create a new JITX project.
- Pull in major components to your JITX project.
- Import your components from an existing design or component file (KiCad or Altium).
- Search OCDB to see if the part exists.
- Connectivity + Passives
netstatements to connect your components together
cap-strap, etc. to create passives, pullups, etc.
- write checks for your components and design, then run them to verify validity of your design
- Export to CAD (Altium or KiCad)
- Layout, Order Board (Conventional EE process)
In the JITX VS Code panel, select "New project".
Create a new directory with a descriptive name like
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.
Explorer icon in the top left corner of VS Code...
...to open and view the files you just created. Take a look at
main.stanzais the main file from which you run your design.
In the new VS Code window, open
In this file, the design is contained in 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:
Which specifies the
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()
With an active cursor in
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.
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.
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:
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
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 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
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
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!
We're now ready to export our design:
- 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.
- Go into the file
helpers.stanzaand scroll down to the
export-to-cad()function. Change the argument that says
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.
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.
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
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
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
10 would be an
pcb-module lm317a-regulator (output-voltage:Double) :
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 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.
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
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 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
inst lm317a : ocdb/components/unisonic/LM317A/component net (vin lm317a.input) net (vout lm317a.output)
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
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)
Continue to the Quickstart : Organize a Schematic.