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 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
JITX Workflow - Idea to Manufacturing
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
- use
net
statements to connect your components together - use
res-strap
,cap-strap
, etc. to create passives, pullups, etc.
- use
- Checks
- 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)
Tutorial - Design a PCB in JITX
1. Create Project
In the JITX VS Code panel, select "New project".
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...
...to open and view the files you just created. Take a look at main.stanza
.
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.
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.
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:
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(in-voltage, adj-voltage, 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!
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 theexport-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
JTIX 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(in-voltage, adj-voltage, 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.