Metadata-Version: 2.1
Name: gnps
Version: 0.6.1
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. Files using `externals` still fail clearly in the Python runtime.

### 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 && uart0.rx_valid == 1 | uart0.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`
- external module signal: `uart0.rx_valid`
- 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`, or `externals` are absent, they default to empty.

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 restores `` `default_nettype wire`` afterward
- 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 now follow `module.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
- 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.

## Examples

See `examples/` for:
- standalone YAML at the repository root, such as `example1.yaml`, `example2.yaml`, `example3.yaml`, `example3io.yaml`, `example3io_b.yaml`, and `example_add.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-oriented YAML in `examples/verilog/`
- FPGA-oriented examples under `examples/fpga/`, including standalone `blink.yaml` and `ledwalk.yaml`, plus dedicated folders for `blink_uart/`, `verilog_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.
- `externals` are not yet supported by the Python backend or runtime simulator.
- 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`, Codex has been used to help with code-generation and refactoring work in this repository.
