Metadata-Version: 2.1
Name: gnps
Version: 0.6.10
Summary: Generalized Numerical P Systems simulator package
Author-Email: Sergey Verlan <dont-spam-me@no.spam>
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Development Status :: 4 - Beta
Requires-Python: >=3.10
Requires-Dist: lark~=1.1
Requires-Dist: PyYAML~=6.0
Description-Content-Type: text/markdown

# GNPS Project

GNPS (Generalized Numerical P Systems) is a Python package for parsing and simulating numerical P systems, and for exporting them to generated code.

## Current Capabilities
- Python simulation of standalone and import-composed GNPS YAML systems
- Python source export through `gnps-transform -t python`, including import-composed systems as a single generated file
- Verilog/SystemVerilog export through `gnps-transform -t verilog`
- Webots Python controller export through `gnps-transform -t webots`
- Optional import/composition metadata for Verilog generation

## CLI

### Simulator
```powershell
python -m gnps <gnps_file.yaml> [input.csv] [output.csv] [options]
```

Options:
- `-c`, `--compute_mode`: run in continuous compute mode
- `-s`, `--steps N`: number of steps to run
- `--csv`: emit CSV output in compute mode

Mode behavior:
- IO mode is the default. It reads CSV input rows and writes CSV output rows.
- In IO mode, the simulator always expects CSV input and produces CSV output.
- Compute mode does not consume CSV input. It runs the system for a fixed number of steps and prints the output state.
- `--csv` only affects compute mode and makes the output CSV-formatted.

The simulator supports standalone GNPS YAML and import-composed GNPS systems. Python simulation and `gnps-transform -t python` support GNPS imports only; they do not consume `verilog.externals`.

### Transformer
```powershell
gnps-transform <gnps_file.yaml> -t {python,verilog,webots} [options]
```

Options:
- `-o`, `--output-dir DIR`: output directory
- `--output-suffix SUFFIX`: suffix inserted before the generated file extension
- `--import-path DIR`: extra import search path, may be repeated
- `--import-paths LIST`: path-separated import search list
- `-v`, `--verbose`: verbose logging

Import resolution order:
1. relative to the importing YAML file
2. `--import-path` / `--import-paths` directories in the order provided

## YAML Schema

Standalone GNPS YAML files are still supported:

```yaml
cells:
  - id: 1
    contents:
      - x = 0
      - y = 1
    input: [x]
    output: [y]

rules:
  - x + 1 -> y
```

Verilog/SystemVerilog-oriented extensions are optional:

```yaml
module:
  name: controller_top
  zero_reset_mode: false

constants:
  CLOCK_HZ: 27000000
  BLINK_HZ: 2
  THRESHOLD: 5
  HALF_PERIOD: CLOCK_HZ / (2 * BLINK_HZ)

aliases:
  sensed: sensor0.level

imports:
  - module: sensor.yaml
    as: sensor0
    connections:
      raw: sample

verilog:
  clock: clk
  reset: rst
  real_encoding:
    kind: fixed_point
    signed: true
    width: 32
    frac_bits: 16
  ports:
    sample:
      direction: input
      width: 16
    alarm:
      direction: output
      width: 16
  externals:
    uart0:
      header: uart.header.yaml
      parameters:
        FIFO_DEPTH: 16
      connections:
        rx: uart_rx
        tx: uart_tx

cells:
  - id: 1
    contents:
      - sample = 0
      - alarm = 0
    output: [alarm]

rules:
  - sensed > THRESHOLD && rx_valid == 1 | rx_data + 1 -> alarm
```

Additional YAML sugar is also supported:
- top-level `if` / `then` / `else` blocks
- recursive nested `if` blocks anywhere a rule list is allowed
- `fsm:` blocks with one or more FSMs per module

Example:
```yaml
fsm:
  - name: ctrl
    variable: ctrl_state
    initial: IDLE
    states:
      - IDLE:
          rules:
            - if: start > 0
              then: RUN -> ctrl_state
      - RUN:
          rules:
            - if: done > 0
              then: IDLE -> ctrl_state
```

Single-item sugar is accepted in branch bodies, so these are equivalent:
```yaml
then:
  - 1 -> y
```

```yaml
then: 1 -> y
```

Qualified references supported by the parser:
- local variable: `x`
- imported GNPS IO: `sensor0.level`
- Verilog port names are not part of GNPS alias resolution

## Defaults

If `module` is absent, the effective defaults are:
- module name: source filename stem
- `zero_reset_mode`: `false`
- `real_encoding.kind`: `fixed_point`
- `real_encoding.signed`: `true`
- `real_encoding.width`: `32`
- `real_encoding.frac_bits`: `16`
- clock name: `clk`
- reset name: `rst`
- reset polarity: active high
- Verilog `ports`: none

If `constants`, `aliases`, `imports`, `verilog.externals`, or `webots.bindings` are absent, they default to empty where the selected backend allows them.

Constants may be literal numbers or load-time constant expressions. Expressions are evaluated in declaration order and may reference only previously declared constants:

```yaml
constants:
  A: 2 * 5
  B: 3 * A + 1
```

These defaults are also documented in `rules.md`.

Verilog backend-specific sections:

```yaml
verilog:
  clock: clk
  reset: rst
  real_encoding:
    kind: fixed_point
    signed: true
    width: 32
    frac_bits: 16
  ports:
    uart_rx:
      direction: input
      width: 1
    uart_tx:
      direction: output
      width: 1
  externals:
    uart0:
      header: uart.header.yaml
      parameters:
        FIFO_DEPTH: 16
      connections:
        rx: uart_rx
        tx: uart_tx
```

Verilog port entries may also specify `kind`; it defaults to `logic` when omitted.
The `clock` and `reset` fields are scalar names for the generated boundary signals.
They are not GNPS aliases, but external module connections may still wire to them
through `verilog.externals.connections`.

Webots backend-specific sections:

```yaml
webots:
  controller_name: e_puck_pid_controller
  timestep: 64
  bindings:
    left_sensor:
      device: ps0
      read_method: getValue
    right_sensor:
      device: ps7
      read_method: getValue
    left_speed:
      device: left wheel motor
      write_method: setVelocity
    right_speed:
      device: right wheel motor
      write_method: setVelocity
    left_position:
      device: left wheel motor
      write_method: setPosition
    right_position:
      device: right wheel motor
      write_method: setPosition
  init:
    left_position: inf
    right_position: inf
```

## Generated RTL

The `verilog` backend emits SystemVerilog-style RTL:
- file extension: `.sv`
- wraps each generated module with `` `default_nettype none`` and keeps it in effect throughout the generated file
- module parameters in the module header
- `always_comb` for next-state logic
- `always_ff` for sequential updates
- fixed-point values stay as integer literals in the emitted RTL, wrapped in generated module-local `localparam` aliases such as `_VAL_1_0`
- generated fixed-point state, helper signatures, and literals follow `verilog.real_encoding.signed` instead of always being emitted as signed values
- boundary conversions use generated integer-only helper functions rather than `real`-based helpers
- if a GNPS input/output is described in `verilog.ports`, the generated RTL converts between the module-local fixed-point encoding and the declared Verilog port format at the module boundary
- if a GNPS module does not declare `verilog.ports`, the Verilog backend infers the full module boundary from that module's input/output variables and `verilog.real_encoding`
- if a GNPS input/output is described in `verilog.externals`, the generated RTL rewires that variable internally to the external module rather than exposing it at the top-level interface; if the target is a GNPS output that is also a top-level port, the top-level port is driven directly from the external module output
- plain variable names such as `sample` or `alarm` refer to the local GNPS variable, while Verilog port names are emitted only through `verilog.ports`
- fixed-point to integer top-port conversion truncates toward zero
- generated literal aliases are documented with comments showing the original source values and fixed-point format

Supported expression subset:
- constants
- local variables
- qualified references
- addition and subtraction
- unary minus
- constant multiplication and constant division
- boolean comparisons
- boolean `&&`, `||`, `!`

Rejected constructs:
- generic function calls
- variable-by-variable multiplication
- non-constant division
- arrays

Each GNPS module uses its own `real_encoding`. Boundary conversions are inserted automatically for imported GNPS IO and external ports.
In the Verilog backend, consumed variables are reset to zero explicitly before productions are accumulated.

## Generated Webots Controllers

The `webots` backend emits a single Python controller file:
- file extension: `.py`
- embeds the generated Python GNPS model without the standalone CSV CLI
- creates a Webots `Robot`
- binds GNPS input variables to configured Webots device read methods
- binds GNPS output variables and initialization values to configured device write methods
- uses `webots.timestep` when provided, otherwise reads the basic timestep from the robot

Each declared GNPS input must have a binding with `read_method`.
Each declared GNPS output must have a binding with `write_method`.
Initialization entries under `webots.init` must refer to bindings with `write_method`.

The backend does not generate Webots world or PROTO files. It only emits the
controller glue that reads Webots devices, advances the generated GNPS model,
and writes outputs back to Webots devices.

## Backend Boundaries

- Python simulation and `gnps-transform -t python` use GNPS model semantics and support GNPS imports only.
- Python simulation and the Python backend do not consume `verilog.externals`.
- Verilog generation uses the GNPS model plus `verilog.real_encoding`, `verilog.ports`, `verilog.externals`, and GNPS imports.
- Webots generation uses the GNPS model plus `webots.bindings` / `webots.init`.
- Webots generation emits controller code only: one Python controller for the root YAML file, with no world, PROTO, or external RTL files.

## Examples

See `examples/` for:
- standalone YAML under `examples/simple/`, such as `example1.yaml`, `example2.yaml`, `example3.yaml`, `example3io.yaml`, `example3o.yaml`, `example_add.yaml`, `ballistic.yaml`, and `ballistic_if.yaml`
- recursive conditional YAML in `examples/fsm/if_recursive.yaml`
- FSM-oriented YAML in `examples/fsm/fsm_counter.yaml` and `examples/fsm/fsm_dual.yaml`
- import-only Python composition in `examples/composition/python_composed/`
- imported multicell Python composition in `examples/composition/imported_composition/`
- Verilog composition examples in `examples/composition/verilog_composed/`
- FPGA-oriented examples under `examples/fpga/`, including standalone `blink.yaml`, `blink_if.yaml`, and `ledwalk.yaml`, plus dedicated folders for `blink_uart/`, `sensor_controller/`, `fpga_uart_led/`, `fpga_spi_gpio_bridge/`, and `axii/`
- Webots controller examples under `examples/webots/`, including `e_puck_pid/` and `pioneer3_dx_obstacle_avoidance/`

## Notes
- Top-level `name` and `description` are treated as metadata and ignored by Verilog generation.
- Python composition currently supports `imports` only.
- `verilog.externals` are not part of the Python backend or runtime simulator configuration.
- Declaring a variable in `output` only exposes it at the module boundary; it does not implicitly consume or reset that variable each step. If an output should behave like a per-step pulse/value rather than accumulated state, add an explicit consume/reset rule for it.
- `module.zero_reset_mode: true` changes only that module's local variables: they are cleared to zero at the start of every step before productions are accumulated. Imported modules keep their own mode independently.
- Detailed behavior and schema rules live in `rules.md`.

Example:

```yaml
cells:
  - id: 1
    contents:
      - alarm = 0
    output: [alarm]

rules:
  # Persistent output/state: alarm keeps its previous value unless consumed
  - sensor0.level > 2 | 1 -> alarm
```

```yaml
cells:
  - id: 1
    contents:
      - alarm = 0
    output: [alarm]

rules:
  # Per-step output: first produce the value you want
  - sensor0.level > 2 | 1 -> alarm
  # Then consume/reset alarm each step so it does not accumulate
  - alarm * 0 -> alarm
```

Since version `0.3.0`, AI has been used to help with code-generation and refactoring work in this repository.
