PJDL Hardware Module
January 29, 2026 · View on GitHub
This is a hardware implementation of the PJDL data link. PJDL is a single-wire data link protocol and belongs to the PJON network protocol.
This hardware module was writen as part of my bachelors project at ETH.
An example of a complete processor including this module can be found on the PJON_ON_CROC repository.
Why
The PJON protocol including the PJDL data link in the main repository is written in C++. This is great as it is very flexible and allows the protocol to run on a wide range of different devices. It however brings the disadvantage, that the micro controllers have to poll often on the bus, to not miss anything coming in. They also need a lot of time sending and receiving data. The hardware module in this repository can be used in a processor architecture to take the PJDL sending and receiving tasks from the processor, and do these tasks in the hardware directly. The module can also be used in FPGA logic implementations which need the PJDL-protocol or even hardware sensor which should be connected to PJDL.
Architecture
The PJDL module is divided into two sub modules. One for sending and one for receiving. The complete module can be seen in figure 5.2. The two sub-modules are interconnected in a way, that they disable each other if the are running. This is to prevent sending when the bus is already busy, as well as to prevent receiving its own data. Apart from this interconnection, there is also the "start_ack_receiving"-connection, which lets the receving module know, when it should start receiving an acknowledgment. This then activates the receiving module even if the enable pin is still low and the receiving module starts receiving the acknowledgment. All the other signals are routed directly out from the PJDL module and are described in the next subsection.
Module-Interfaces
The following inputs and outputs are present on the PJDL module:
clk_i,rst_ni: basic timing and reset signals from the parent modulesaxis_read_req_i,axis_read_rsp_o: axi-stream interface, data sent to this interface will be sent out over PJDL -> AXI stream protocol is explained in more detailed later on in this sectionaxis_write_rsp_i,axis_write_req_o: axi-stream interface, data received over PJDL is sent out over this interface -> AXI stream protocol is explained in more detailed laterpjdl_spec_preamble_i,pjdl_spec_pad_i,pjdl_spec_data_i,pjdl_spec_acceptance_i: Timing specifications for the PJDL protocol. These signals are meant as settings and shouldn’t be changed often. The settings should be set according to the PJDL specification in table 2.1. Small deviations from the specified values can help to mitigate delays coming from the I/O-logic.sending_in_progress_o,receiving_in_progress_o: signals allowing a parent signal to read out the current state of the PJDL module. Signals are high if the modules are currently sending or receiving datapjon_i,pjon_o,pjon_en_o: The actual PJDL interface -> individual pins described in more detail later
AXI-stream interface
AXI-Stream interfaces where chosen for their simplicity and to have a clear standard to follow. The AXI-Stream interfaces where designed according to the AMBA AXI-Stream specification by arm. This document only describes how AXI-stream was used for this project:
For our AXI-Stream interface we use 8 data bits on TDATA to transfer one byte at a time.
The TLAST signal of the AXI-stream bus is used in our case, to signal the end of a PJDL-frame.
TSTRB isn’t used as we do not need any position bytes. All bytes received over
the AXI stream interface are currently interpreted as data bytes.
TID, TDEST and TWAKEUP aren’t used either and their values are ignored.
The TKEEP value isn’t used by the PJDL-receiver. The sender in contrast ignores packets
if the TKEEP is kept low. If TKEEP is low while TLAST is high, the end of the frame is still
recognized.
The TUSER signal is not used by the PJDL-receiver. The PJDL-sender on the other hand
uses two bits of user signals for additional information:
Bit 0 indicates the sending module that the corresponding byte is an acknowledgment.
This is important for the sending module, as the data is then synced to the busy signal on
the PJDL input and sent without a frame initialization. TLAST should always be active
as well if this bit is set, as the acknowledgment is always the last bit of its transfer.
Bit 1 indicates an acknowledgment request. If this bit is set, the PJDL-sender starts
creating a signal to keep the bus busy until it sees a change on the PJDL-input. As soon
as this is the case, it indicates the receiving module to start receiving which will then
start receiving the acknowledgment coming from the other side. TLAST should also be
high if bit 1 is set.
Bit 0 and 1 should never be set at the same time.
The AXI-stream interface allows to signal in both direction, whether data is ready to be
sent and valid to receive. It is important to remember though, that the PJDL interface
can’t stall its transmission. For a certain time, the data can be buffered in the PJDL-modules
internal buffer (see parameters further down). As soon as the receiving buffer
is full, all new data arriving on the PJDL bus is ignored and the PJON layer 3 communication will be corrupted.
The same goes for the sending buffer. If it is completely empty, the PJDL module sends
bytes with zero values, which will corrupt the layer 3 transfer as well.
PJDL interface
The PJDL bus is mostly used with a single wire. To keep as many options open as possible, it was still implemented with two separate lines for this module. With an additional output enable, the module indicates whether it is currently outputting something or only listening. That way, PJDL can be used on two separate lines or combined together to a single line with some simple logic. If all signals are actually routed to the outside of a chip, this allows even for more advanced sending and receiving circuits with additional amplifiers, filters and different voltages.
Module Parameters
The PJDL module has three module parameters that can be set. In the following, each of these parameters is explained.
BufferSize
The PJDL module features one buffer in each direction on the AXI-stream interfaces. This allows to buffer data that was received or is ready to be sent. The buffer size can be configured with the "BufferSize" parameter. The default value is set to two and it can be lowered to a minimum of one.
AXI Stream types
The axis_req_t and axis_rsp_t type parameters are the inputs for the AXI-stream types. They define the with of all the signals that are part of the axi-stream interface. Of course all the different sizes have to be set in the correct way as described above, to work properly. Still having these types as parameters makes it easier as the typedefs only have to be done once. To define them in the correct way, the following code segment can be used:
PJON Addressing
PJON-addressing is a seperate module added to the project later. It is not part of the PJDL module but can be connected directly in series to the PJON-Module (on the axi-stream connection).
The purpose of the PJON-Addressing module is to filter out data that is not meant for the current device. In PJON networks with a lot of devices this can increase the performance significantly.
PJON-addressing is designed as a seperate module as its functionallity belongs to layer 3 of PJON and thus not to PJDL.
The pjon_addressing module inspects always the first packet of every transfer coming
from the PJDL module. This first packet contains the id of the receiving device. If the
address is correct, the packet is forwarded further through axi-stream. If the address is wrong and the
packet is thus meant for an other device, the pjon-addressing-module drops the packet
and doesn’t forward it.
PJON also implements an option to broadcast messages to many
devices. This is done with the broadcast id 255. Every packet received for this address is thus also accepted and forwarded.
The original library includes also an option to deactivate the address filtering and receive
all packets coming in. This is useful for debugging purposes and if the device should be
used as a router. The functionality is called "router mode" in the software implementation
and we included it in our implementation as well. It can be activated with an input
signal to the pjon-addressing module.
PJON-Adressing Inputs and Outputs
The following table lists all the input and outputs of the pjon-addressing module:
| Signal | Direction | Width / Type | Description |
|---|---|---|---|
axis_read_req_i | input | axis_req_t | AXI‑Stream input from wrapper |
axis_read_rsp_o | output | axis_rsp_t | AXI‑Stream response to wrapper |
axis_read_req_o | output | axis_req_t | AXI‑Stream request to wrapper |
axis_read_rsp_i | input | axis_rsp_t | AXI‑Stream response from wrapper |
axis_read_req_i | input | axis_req_t | AXI‑Stream input from layer 2 module |
axis_read_rsp_o | output | axis_rsp_t | AXI‑Stream response to layer 2 module |
axis_read_req_o | output | axis_req_t | AXI‑Stream request to layer 2 module |
axis_read_rsp_i | input | axis_rsp_t | AXI‑Stream response from layer 2 module |
pjon_device_id_i | input | logic, 8 bit | 8‑bit PJON device-id |
router_mode_i | input | logic, 1 bit | Activates router mode (accepts all packets) |
It is important to mention, that the axi-stream connection in sending direction is directly routed through the module without any change or buffer in between. The choice to still route these signals through the modules was made to simplify possible future addition of PJON layer 3 functionality inside the addressing module.
PJON-Addressing Parameters
There are also a few parameters the pjon_addressing module can be configured with:
- AXI-Stream types: Parameter inputs for the AXI-stream types. This helps to keep the AXI-stream type the same over all different modules and makes it easy to change small details on the bus if need be.
- Buffer Size: The pjon_addressing module includes a buffer in the receiving direction, as the data is actively processed in that direction. Per default, the size of the buffer is set to one as this is all that is needed to process that data in real-time. In the current implementation it probably doesn’t make sense to increase its size. Considering that, a buffer wouldn’t have been absolutely necessary and a simple register would be sufficient. It was much simpler to implement it with a buffer tough and this also makes it easy to add more functionality for the layer 3 hardware in the future.
Processor integration
As part of this project, the PJDL module was also integrated into the Croc-SoC, together with a Direct Memory Access Module (DMA). All the necessary libraries to work with PJON where developed as well.
The full project intecrating this module into Croc-SoC can be found in the pjon_on_croc repository.
Bender
This project works with the dependency manager Bender. To make it as easy as possible to reuse this module, the submodules are included in this repository as well. They can be found in the .bender folder. To update them, Bender is required.
License
Unless specified otherwise in the respective file headers, all code checked into this repository is made available under a permissive license. All hardware sources and tool scripts are licensed under the Solderpad Hardware License 0.51 (see LICENSE.md). All software sources are licensed under Apache 2.0.
The files in .bender are from submodules and could have their own licence specified in their respective folders and repositories.