Metadata-Version: 2.1
Name: gnps
Version: 0.3.6
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`
- 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 in continuous mode

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} [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 legacy files still work:

```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
  real_encoding:
    kind: fixed_point
    signed: true
    width: 32
    frac_bits: 16
  clock:
    name: clk
  reset:
    name: rst
    active_high: true
  top_ports:
    - name: uart_rx
      dir: input
      kind: logic
      width: 1
      signed: false

constants:
  THRESHOLD: 5

aliases:
  sensed: sensor0.level

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

externals:
  - header: uart.header.yaml
    as: uart0
    connections:
      clk: top.clk
      rst: top.rst
      rx: top.uart_rx

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 port: `uart0.rx_valid`
- top-level signal: `top.uart_rx`, `top.clk`, `top.rst`

## 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
- top ports: none

If `constants`, `aliases`, `imports`, or `externals` are absent, they default to empty.

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

## 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 has the same name as a declared `top_port`, the generated RTL automatically converts between the module-local fixed-point encoding and the declared top-port integer/logic format at the module boundary
- plain variable names such as `sample` or `alarm` refer to the local GNPS variable, while `top.sample` refers to the raw top-level signal before or after that boundary conversion
- 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 `src/examples/` for:
- standalone legacy-style YAML
- recursive conditional YAML in `if_recursive.yaml`
- FSM-oriented YAML in `fsm_counter.yaml` and `fsm_dual.yaml`
- import-only Python/Verilog composition
- FPGA-oriented examples under `src/examples/fpga/`, including `blink.yaml`, `blink_uart.yaml`, and `ledwalk.yaml`
- imported multicell Verilog composition
- external UART header and controller top module examples

## 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
```
