PySHDL 0.1.3__tar.gz → 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  .gitignore
2
+
2
3
  # Python-generated files
3
4
  __pycache__/
4
5
  *.py[oc]
@@ -13,4 +14,5 @@ wheels/
13
14
 
14
15
  *.c
15
16
  *.so
16
- .python-version
17
+ .python-version
18
+ .DS_Store
@@ -191,6 +191,72 @@ A[1] # LSB of A
191
191
  A[8] # MSB of 8-bit A
192
192
  ```
193
193
 
194
+ ### Constants
195
+
196
+ Constants allow you to define named values that can be used throughout your component definition. This makes your code more maintainable and easier to parameterize.
197
+
198
+ **Syntax:**
199
+
200
+ ```shdl
201
+ CONSTANT_NAME = value;
202
+ ```
203
+
204
+ Where `value` can be:
205
+ - Decimal: `WIDTH = 16;`
206
+ - Binary: `MASK = 0b1111;`
207
+ - Hexadecimal: `FLAGS = 0xFF;`
208
+
209
+ **Rules:**
210
+ - Constant names must be all uppercase with underscores (e.g., `WIDTH`, `BUS_SIZE`, `NUM_GATES`)
211
+ - Any uppercase identifier followed by `= number;` is treated as a constant
212
+ - Constants must be declared inside the component body, before their use
213
+ - Constant values must be non-negative integers
214
+ - Constants are substituted before generator expansion
215
+
216
+ **Usage:**
217
+
218
+ Constants can be used in two ways:
219
+
220
+ 1. **As scalar values** in connections (for constants 0 or 1):
221
+ ```shdl
222
+ TRUE = 1;
223
+ FALSE = 0;
224
+ A -> gate1.A;
225
+ TRUE -> gate1.B; # Ties input B to constant 1
226
+ ```
227
+
228
+ 2. **As bit-indexed vectors** in connections:
229
+ ```shdl
230
+ COMP = 7; # 0b111 in binary
231
+ COMP[1] -> gate1.A; # Bit 1 (LSB) of COMP
232
+ COMP[2] -> gate2.A; # Bit 2 of COMP
233
+ COMP[3] -> gate3.A; # Bit 3 (MSB) of COMP
234
+ ```
235
+
236
+ **Example:**
237
+
238
+ ```shdl
239
+ component ParamAdder(A[16], B[16], Cin) -> (Sum[16], Cout) {
240
+ WIDTH = 16;
241
+ LAST_BIT = 16;
242
+
243
+ # Create WIDTH full adders
244
+ >i[WIDTH]{
245
+ fa{i}: FullAdder;
246
+ }
247
+
248
+ connect {
249
+ # ... connections using WIDTH and LAST_BIT ...
250
+ }
251
+ }
252
+ ```
253
+
254
+ **Use Cases:**
255
+ - Defining bus widths: `BUS_WIDTH = 32;`
256
+ - Setting array sizes: `>i[BUS_WIDTH]{ ... }`
257
+ - Parameterizing generator ranges: `>i[1, WIDTH]{ ... }`
258
+ - Making components more maintainable and reusable
259
+
194
260
  ---
195
261
 
196
262
  ## Imports and Modules
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySHDL
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: SHDL (Simple Hardware Description Language) is a minimal HDL designed for creating digital circuits and easily simulating them. It compiles directly to C for maximum performance and portability. PySHDL is the Python interface for SHDL.
5
5
  Project-URL: Homepage, https://github.com/rafa-rrayes/SHDL
6
6
  Project-URL: Repository, https://github.com/rafa-rrayes/SHDL
@@ -20,6 +20,7 @@ A lightweight hardware description language and Python driver for digital circui
20
20
  - ⚡ **C Backend** - Compiles to optimized C code for fast simulation and portability
21
21
  - 🔧 **Command Line Tools** - Built-in compiler and utilities
22
22
  - 📦 **Component Reuse** - Import and compose reusable circuit components
23
+ - 🔢 **Constants Support** - Use named constants for parameterizable designs
23
24
 
24
25
  ## Installation
25
26
 
@@ -128,7 +129,7 @@ See the `examples/` directory for more complete examples:
128
129
 
129
130
  ## Documentation
130
131
  Github repository: [rafa-rrayes/SHDL](https://github.com/rafa-rrayes/SHDL)
131
- For more detailed documentation, see [DOCS.md](DOCS.md).
132
+ For more detailed documentation, see [DOCS.md](https://github.com/rafa-rrayes/SHDL/blob/master/DOCS.md)
132
133
 
133
134
  ## Requirements
134
135
 
@@ -9,6 +9,7 @@ A lightweight hardware description language and Python driver for digital circui
9
9
  - ⚡ **C Backend** - Compiles to optimized C code for fast simulation and portability
10
10
  - 🔧 **Command Line Tools** - Built-in compiler and utilities
11
11
  - 📦 **Component Reuse** - Import and compose reusable circuit components
12
+ - 🔢 **Constants Support** - Use named constants for parameterizable designs
12
13
 
13
14
  ## Installation
14
15
 
@@ -117,7 +118,7 @@ See the `examples/` directory for more complete examples:
117
118
 
118
119
  ## Documentation
119
120
  Github repository: [rafa-rrayes/SHDL](https://github.com/rafa-rrayes/SHDL)
120
- For more detailed documentation, see [DOCS.md](DOCS.md).
121
+ For more detailed documentation, see [DOCS.md](https://github.com/rafa-rrayes/SHDL/blob/master/DOCS.md)
121
122
 
122
123
  ## Requirements
123
124
 
@@ -0,0 +1,23 @@
1
+ use stdgates::{AND};
2
+
3
+ component Comp7(A[3]) -> (True) {
4
+ COMP = 7; # 0b111
5
+
6
+ >i[3]{
7
+ and{i}: AND;
8
+ }
9
+ and_out1: AND;
10
+ and_out2: AND;
11
+
12
+ connect {
13
+ >i[3]{
14
+ A[{i}] -> and{i}.A;
15
+ COMP[{i}] -> and{i}.B;
16
+ }
17
+ and1.O -> and_out1.A;
18
+ and2.O -> and_out1.B;
19
+ and_out1.O -> and_out2.A;
20
+ and3.O -> and_out2.B;
21
+ and_out2.O -> True;
22
+ }
23
+ }
@@ -1,5 +1,14 @@
1
1
  from PySHDL import Circuit
2
2
 
3
+ def test_comp7():
4
+ circuit = Circuit("examples/SHDL_components/compare7.shdl")
5
+
6
+ for val in range(8):
7
+ circuit.poke("A", val)
8
+ circuit.step(10)
9
+ result = circuit.peek("True")
10
+ print(f"Compare7 input: {val}, output: {result}")
11
+
3
12
  def test_register16():
4
13
  circuit = Circuit("examples/SHDL_components/reg16.shdl")
5
14
 
@@ -37,4 +46,5 @@ if __name__ == "__main__":
37
46
  result = circuit.peek("Sum")
38
47
 
39
48
  print(f"{A} + {B} + {Cin} = {result}, expected {A + B + Cin}")
40
- test_register16()
49
+ test_register16()
50
+ test_comp7()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "PySHDL"
3
- version = "0.1.3"
3
+ version = "0.1.4"
4
4
  description = "SHDL (Simple Hardware Description Language) is a minimal HDL designed for creating digital circuits and easily simulating them. It compiles directly to C for maximum performance and portability. PySHDL is the Python interface for SHDL."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,4 +1,4 @@
1
- """PySHDL library."""
1
+ """shdl library."""
2
2
 
3
3
  from importlib import import_module
4
4
  from importlib.metadata import PackageNotFoundError, version
@@ -16,6 +16,6 @@ __all__ = [
16
16
  "__version__",
17
17
  ]
18
18
  try:
19
- __version__ = version("PySHDL")
19
+ __version__ = version("shdl")
20
20
  except PackageNotFoundError:
21
21
  __version__ = "0.0.0"
@@ -59,6 +59,7 @@ class Component:
59
59
  instances: List[Instance] = field(default_factory=list)
60
60
  connections: List[Tuple[str, str]] = field(default_factory=list) # (from, to)
61
61
  imports: Dict[str, List[str]] = field(default_factory=dict) # module -> [components]
62
+ constants: Dict[str, int] = field(default_factory=dict) # constant_name -> value
62
63
 
63
64
 
64
65
  class SHDLParser:
@@ -142,6 +143,9 @@ class SHDLParser:
142
143
  # Parse outputs
143
144
  comp.outputs = self._parse_ports(outputs_str, is_input=False)
144
145
 
146
+ # Parse constants
147
+ comp.constants = self._parse_constants(body)
148
+
145
149
  # Parse body (instances and connections)
146
150
  self._parse_body(comp, body)
147
151
 
@@ -171,11 +175,81 @@ class SHDLParser:
171
175
 
172
176
  return ports
173
177
 
178
+ def _parse_constants(self, body: str) -> Dict[str, int]:
179
+ """
180
+ Parse constant declarations from component body.
181
+ Syntax: NAME = value;
182
+ Example: WIDTH = 16; or COMP = 0b111; or MASK = 0xFF;
183
+ Constants are identified by uppercase names.
184
+ Supports decimal, binary (0b), and hexadecimal (0x) literals.
185
+ """
186
+ constants = {}
187
+
188
+ # Match: UPPERCASE_NAME = value;
189
+ # Constants must be all uppercase with optional underscores
190
+ # Value can be: decimal (123), binary (0b1010), or hex (0xAF)
191
+ pattern = r'([A-Z_][A-Z0-9_]*)\s*=\s*(0[bB][01]+|0[xX][0-9a-fA-F]+|\d+)\s*;'
192
+
193
+ for match in re.finditer(pattern, body):
194
+ name = match.group(1)
195
+ value_str = match.group(2)
196
+
197
+ # Parse the value based on prefix
198
+ if value_str.startswith(('0b', '0B')):
199
+ value = int(value_str[2:], 2) # Binary
200
+ elif value_str.startswith(('0x', '0X')):
201
+ value = int(value_str[2:], 16) # Hexadecimal
202
+ else:
203
+ value = int(value_str) # Decimal
204
+
205
+ constants[name] = value
206
+
207
+ return constants
208
+
209
+ def _add_constant_drivers(self, comp: Component, body: str):
210
+ """
211
+ Build a mapping of constant bit signals to their values (VCC/GND).
212
+ This will be used to resolve references during connection parsing.
213
+ """
214
+ # Find all synthetic constant bit signals in the body
215
+ const_bit_pattern = r'__const_([A-Z_][A-Z0-9_]*)_bit_(\d+)'
216
+ matches = re.findall(const_bit_pattern, body)
217
+
218
+ # Build mapping: signal_name -> '__VCC' or '__GND'
219
+ const_bit_map = {}
220
+
221
+ for const_name, bit_str in matches:
222
+ bit_index = int(bit_str)
223
+
224
+ # Get the constant value
225
+ if const_name not in comp.constants:
226
+ continue
227
+
228
+ const_value = comp.constants[const_name]
229
+
230
+ # Extract the bit value (1-indexed, LSB is bit 1)
231
+ bit_value = (const_value >> (bit_index - 1)) & 1
232
+
233
+ # Create a signal name for this constant bit
234
+ signal_name = f'__const_{const_name}_bit_{bit_index}'
235
+
236
+ # Map to VCC or GND
237
+ const_bit_map[signal_name] = '__VCC' if bit_value == 1 else '__GND'
238
+
239
+ # Store the mapping in the component for use during connection parsing
240
+ comp.constants_bit_map = const_bit_map
241
+
174
242
  def _parse_body(self, comp: Component, body: str):
175
243
  """Parse component body (instances and connections)."""
176
- # Expand generators first
244
+ # Substitute constants first
245
+ body = self._substitute_constants(body, comp.constants)
246
+
247
+ # Expand generators
177
248
  body = self._expand_generators(body)
178
249
 
250
+ # Add constant bit drivers AFTER generator expansion
251
+ self._add_constant_drivers(comp, body)
252
+
179
253
  # Split into instances and connections
180
254
  connect_match = re.search(r'connect\s*\{(.*)\}', body, re.DOTALL)
181
255
 
@@ -192,6 +266,44 @@ class SHDLParser:
192
266
  # Parse connections
193
267
  self._parse_connections(comp, connections_part)
194
268
 
269
+ def _substitute_constants(self, body: str, constants: Dict[str, int]) -> str:
270
+ """
271
+ Substitute constant names with their values in the body.
272
+ Constants can be used:
273
+ 1. As scalar values in connections: TRUE -> a1.B; (becomes __VCC or __GND)
274
+ 2. As bit-indexed vectors: COMP[{i}] -> gate.A; (becomes __const_COMP_bit_N)
275
+ """
276
+ # Remove constant declarations from the body
277
+ body = re.sub(r'[A-Z_][A-Z0-9_]*\s*=\s*(?:0[bB][01]+|0[xX][0-9a-fA-F]+|\d+)\s*;', '', body)
278
+
279
+ # For each constant, we need to handle two cases:
280
+ # 1. Direct usage: CONSTANT_NAME (not followed by [)
281
+ # 2. Bit-indexed usage: CONSTANT_NAME[index]
282
+
283
+ for const_name, const_value in constants.items():
284
+ # Case 1: Bit-indexed usage CONST[N] - convert to synthetic signal name
285
+ # CONST[1] becomes __const_CONST_bit_1, CONST[2] becomes __const_CONST_bit_2, etc.
286
+ def replace_bit_index(match):
287
+ bit_index = match.group(1)
288
+ return f'__const_{const_name}_bit_{bit_index}'
289
+
290
+ # Replace CONST[...] with synthetic signal name
291
+ body = re.sub(rf'\b{const_name}\[([^\]]+)\]', replace_bit_index, body)
292
+
293
+ # Case 2: Direct scalar usage - replace with __VCC or __GND for 0/1,
294
+ # or leave as numeric value for other constants
295
+ # Use word boundaries to avoid partial matches
296
+ if const_value == 0:
297
+ body = re.sub(r'\b' + const_name + r'\b', '__GND', body)
298
+ elif const_value == 1:
299
+ body = re.sub(r'\b' + const_name + r'\b', '__VCC', body)
300
+ else:
301
+ # For other values, replace with the numeric value
302
+ # This is useful for parameterization in generators
303
+ body = re.sub(r'\b' + const_name + r'\b', str(const_value), body)
304
+
305
+ return body
306
+
195
307
  def _expand_generators(self, body: str) -> str:
196
308
  """
197
309
  Expand generator syntax:
@@ -269,9 +381,19 @@ class SHDLParser:
269
381
  # Match: signal -> port;
270
382
  pattern = r'([^\s;]+)\s*->\s*([^\s;]+)\s*;'
271
383
 
384
+ # Get the constant bit mapping if it exists
385
+ const_bit_map = getattr(comp, 'constants_bit_map', {})
386
+
272
387
  for match in re.finditer(pattern, connections_str):
273
388
  from_sig = match.group(1).strip()
274
389
  to_sig = match.group(2).strip()
390
+
391
+ # Resolve constant bit signals to VCC/GND
392
+ if from_sig in const_bit_map:
393
+ from_sig = const_bit_map[from_sig]
394
+ if to_sig in const_bit_map:
395
+ to_sig = const_bit_map[to_sig]
396
+
275
397
  comp.connections.append((from_sig, to_sig))
276
398
 
277
399
  def _load_imports(self, comp: Component, base_path: Path):
@@ -666,6 +788,17 @@ def generate_c_code(component: Component) -> str:
666
788
  # constant zero for unconnected pins?
667
789
  if src == CONST_ZERO:
668
790
  return '0u'
791
+ # VCC (constant 1)?
792
+ if src == '__VCC':
793
+ return '1u'
794
+ # GND (constant 0)?
795
+ if src == '__GND':
796
+ return '0u'
797
+ # Synthetic constant bit signal?
798
+ if src.startswith('__const_'):
799
+ # These should have been resolved to __VCC or __GND via connections
800
+ # but if we see them here directly, treat as error
801
+ raise ValueError(f'Unresolved constant signal: {src}')
669
802
  # input bit?
670
803
  if is_bit(src):
671
804
  base, idx = parse_bit(src)
@@ -1,8 +1,8 @@
1
1
  version = 1
2
- revision = 2
2
+ revision = 3
3
3
  requires-python = ">=3.9"
4
4
 
5
5
  [[package]]
6
6
  name = "pyshdl"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  source = { editable = "." }
File without changes
File without changes
File without changes
File without changes