Compare commits

..

11 Commits

55 changed files with 678 additions and 2 deletions

4
.gitignore vendored
View File

@ -2,4 +2,6 @@
.vscode .vscode
*.tmp.* *.tmp.*
node_modules node_modules
cardiograph.code-workspace cardiograph.code-workspace
*venv*
*__pycache__

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "src/argparser"] [submodule "src/argparser"]
path = src/opter path = src/opter
url = https://git.nloewen.com/n/argv-parser.git url = https://git.nloewen.com/n/argv-parser.git
[submodule "src/python/opter-py"]
path = src/python/opter-py
url = https://git.nloewen.com/n/opter-py.git

View File

@ -8,6 +8,15 @@ Cardiograph is an imaginary computer. It has three main components:
## Simulator ## Simulator
### Dependencies
Cardiograph is an imaginary computer. It has three main components:
1. the CPU, *Card* (short for 'Completely Analogue Risc Machine')
2. an input-output processor, *IO*
3. a display, *Graph*
## Simulator
### Dependencies ### Dependencies
- Node.js - Node.js

View File

@ -0,0 +1,20 @@
import board
import displayio
import vectorio
import terminalio
from adafruit_display_text import label
group = displayio.Group()
board.DISPLAY.root_group = group
text = "Hello world"
text_area = label.Label(terminalio.FONT, text=text)
text_area.x = 10
text_area.y = 10
group.append(text_area)
palette = displayio.Palette(1)
palette[0] = 0xff00ff
circle = vectorio.Circle(pixel_shader=palette, radius=5, x=5, y=5)
group.append(circle)

View File

@ -0,0 +1,13 @@
#meowbit
import keypad
import board
km = keypad.KeyMatrix(
row_pins = (board.P0, board.P1, board.P2, board.P3),
column_pins = (board.P4, board.P6, board.P8, board.P9) )
while True:
event = km.events.get()
if event:
print(event.key_number, event.released)

View File

@ -0,0 +1,12 @@
import time
import board
import neopixel
pixels = neopixel.NeoPixel(board.A3, 5*5, brightness=0.5, auto_write=True)
for i in range(50):
pixels.fill((50, 0, 0)) # red
time.sleep(0.5)
pixels.fill((0,0,50)) # blue
time.sleep(0.5)
print(i)

Binary file not shown.

View File

@ -0,0 +1,181 @@
# SPDX-FileCopyrightText: 2016 Damien P. George
# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries
# SPDX-FileCopyrightText: 2019 Carter Nelson
# SPDX-FileCopyrightText: 2019 Roy Hooper
#
# SPDX-License-Identifier: MIT
"""
`neopixel` - NeoPixel strip driver
====================================================
* Author(s): Damien P. George, Scott Shawcroft, Carter Nelson, Rose Hooper
"""
import sys
import board
import digitalio
from neopixel_write import neopixel_write
import adafruit_pixelbuf
try:
# Used only for typing
from typing import Optional, Type
from types import TracebackType
import microcontroller
except ImportError:
pass
__version__ = "6.3.15"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_NeoPixel.git"
# Pixel color order constants
RGB = "RGB"
"""Red Green Blue"""
GRB = "GRB"
"""Green Red Blue"""
RGBW = "RGBW"
"""Red Green Blue White"""
GRBW = "GRBW"
"""Green Red Blue White"""
class NeoPixel(adafruit_pixelbuf.PixelBuf):
"""
A sequence of neopixels.
:param ~microcontroller.Pin pin: The pin to output neopixel data on.
:param int n: The number of neopixels in the chain
:param int bpp: Bytes per pixel. 3 for RGB and 4 for RGBW pixels.
:param float brightness: Brightness of the pixels between 0.0 and 1.0 where 1.0 is full
brightness
:param bool auto_write: True if the neopixels should immediately change when set. If False,
`show` must be called explicitly.
:param str pixel_order: Set the pixel color channel order. The default is GRB if bpp is set
to 3, otherwise GRBW is used as the default.
Example for Circuit Playground Express:
.. code-block:: python
import neopixel
from board import *
RED = 0x100000 # (0x10, 0, 0) also works
pixels = neopixel.NeoPixel(NEOPIXEL, 10)
for i in range(len(pixels)):
pixels[i] = RED
Example for Circuit Playground Express setting every other pixel red using a slice:
.. code-block:: python
import neopixel
from board import *
import time
RED = 0x100000 # (0x10, 0, 0) also works
# Using ``with`` ensures pixels are cleared after we're done.
with neopixel.NeoPixel(NEOPIXEL, 10) as pixels:
pixels[::2] = [RED] * (len(pixels) // 2)
time.sleep(2)
.. py:method:: NeoPixel.show()
Shows the new colors on the pixels themselves if they haven't already
been autowritten.
The colors may or may not be showing after this function returns because
it may be done asynchronously.
.. py:method:: NeoPixel.fill(color)
Colors all pixels the given ***color***.
.. py:attribute:: brightness
Overall brightness of the pixel (0 to 1.0)
"""
def __init__(
self,
pin: microcontroller.Pin,
n: int,
*,
bpp: int = 3,
brightness: float = 1.0,
auto_write: bool = True,
pixel_order: str = None
):
if not pixel_order:
pixel_order = GRB if bpp == 3 else GRBW
elif isinstance(pixel_order, tuple):
order_list = [RGBW[order] for order in pixel_order]
pixel_order = "".join(order_list)
self._power = None
if (
sys.implementation.version[0] >= 7
and getattr(board, "NEOPIXEL", None) == pin
):
power = getattr(board, "NEOPIXEL_POWER_INVERTED", None)
polarity = power is None
if not power:
power = getattr(board, "NEOPIXEL_POWER", None)
if power:
try:
self._power = digitalio.DigitalInOut(power)
self._power.switch_to_output(value=polarity)
except ValueError:
pass
super().__init__(
n, brightness=brightness, byteorder=pixel_order, auto_write=auto_write
)
self.pin = digitalio.DigitalInOut(pin)
self.pin.direction = digitalio.Direction.OUTPUT
def deinit(self) -> None:
"""Blank out the NeoPixels and release the pin."""
self.fill(0)
self.show()
self.pin.deinit()
if self._power:
self._power.deinit()
def __enter__(self):
return self
def __exit__(
self,
exception_type: Optional[Type[BaseException]],
exception_value: Optional[BaseException],
traceback: Optional[TracebackType],
):
self.deinit()
def __repr__(self):
return "[" + ", ".join([str(x) for x in self]) + "]"
@property
def n(self) -> int:
"""
The number of neopixels in the chain (read-only)
"""
return len(self)
def write(self) -> None:
""".. deprecated: 1.0.0
Use ``show`` instead. It matches Micro:Bit and Arduino APIs."""
self.show()
def _transmit(self, buffer: bytearray) -> None:
neopixel_write(self.pin, buffer)

View File

@ -0,0 +1,20 @@
# Open the binary file in read-binary mode
with open('test-multiply.bin', 'rb') as file:
# Read the entire file contents
binary_data = file.read()
# Convert the binary data to a string of hex bytes
hex_string = binary_data.hex()
# Print the hex string
print(hex_string)
# Open the binary file in read-binary mode
with open('test-multiply.bin', 'rb') as file:
# Read the entire file contents into a bytearray
byte_data = bytearray(file.read())
# Print the bytearray
print(byte_data)

Binary file not shown.

View File

@ -0,0 +1,10 @@
import board
import busio
from adafruit_ht16k33 import matrix
i2c = busio.I2C(board.GP17, board.GP16) # scl, sda
matrix = matrix.Matrix8x8(i2c)
matrix.fill(0) # Clear the matrix.
matrix[0, 0] = 1
matrix.brightness = 1
matrix.blink_rate = 2

View File

@ -0,0 +1,14 @@
"""Example for pi pico. Blinking LED"""
import board
import digitalio
import time
led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT
led.value = True
for i in range(50):
led.value = not led.value
time.sleep(0.5)

View File

@ -0,0 +1,17 @@
"""Example for pi pico. Button-controlled LED.
Wiring: switch to ground pin on pico, and to pin 18.
"""
import board
import digitalio
led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT
switch = digitalio.DigitalInOut(board.GP18)
switch.direction = digitalio.Direction.INPUT
switch.pull = digitalio.Pull.UP
while True:
led.value = not switch.value

View File

@ -0,0 +1,19 @@
# pi pico
import keypad
import board
keymatrix = keypad.KeyMatrix(
row_pins = (board.GP5, board.GP6, board.GP7, board.GP8),
column_pins = (board.GP9, board.GP10, board.GP11, board.GP12, board.GP13) )
keymap = {
15:"0", 16:"1", 17:"2", 18:"3", 19:"runhalt",
10:"4", 11:"5", 12:"6", 13:"7", 14:"step",
5:"8", 6:"9", 7:"A", 8:"B", 9:"addrdata",
0:"C", 1:"D", 2:"E", 3:"F", 4:"NA" }
while True:
event = keymatrix.events.get()
if event:
print(event.key_number, event.released, keymap[event.key_number])

View File

@ -0,0 +1,19 @@
import board
import keypad
keymatrix = keypad.KeyMatrix(
row_pins = (board.GP5, board.GP6, board.GP7, board.GP8),
column_pins = (board.GP9, board.GP10, board.GP11, board.GP12, board.GP13) )
keymap = {
15:"0", 16:"1", 17:"2", 18:"3", 19:"runhalt",
10:"4", 11:"5", 12:"6", 13:"7", 14:"step",
5:"8", 6:"9", 7:"A", 8:"B", 9:"addr",
0:"C", 1:"D", 2:"E", 3:"F", 4:"data" }
while True:
keypad_event = keymatrix.events.get()
keyPressed = True if (keypad_event and keypad_event.released ) else False
key = keymap[keypad_event.key_number] if keyPressed else False
if key:
print(key)

View File

@ -0,0 +1,14 @@
"""Example for pi pico. Blinking LED"""
import board
import digitalio
import time
from tm1637_display import TM1637Display
display_1 = TM1637Display(board.GP0, board.GP1, length=4)
display_1.print("1234")
display_2 = TM1637Display(board.GP2, board.GP3, length=4)
display_2.print("ABCD")
print("end")

View File

@ -0,0 +1,31 @@
BSD 3-Clause License
Copyright (c) 2013-2024, Kim Davies and contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

164
python/cpu.py Normal file
View File

@ -0,0 +1,164 @@
import time
class CPU:
def __init__(self):
self.running = False
self.IP = 254
self.acc = 0
self.flags = { 'C': False, 'Z': False, 'N': False, 'Eq': False }
self.instruction = { 'opcode': False, 'operand': False }
self.memory = False
def load_memory(self, bytes):
self.memory = bytes + bytearray(256 - len(bytes))
# print(self.memory)
def start(self):
self.running = True
def step(self):
if self.IP >= 255: # TODO CHECK
self.IP = 0
print("IP:", toHex(self.IP))
self.instruction['opcode'] = self.memory[self.IP]
self.IP = self.IP+1
self.instruction['operand'] = self.memory[self.IP]
self.IP = self.IP+1
self.nums2mnems[self.instruction['opcode']](self, self.instruction['operand'])
print("instr:", toHex(self.instruction['opcode']), toHex(self.instruction['operand']))
print("mnem:", self.nums2mnems[self.instruction['opcode']])
print("acc:", self.acc, "N:", self.flags['N'])
print("running:", self.running)
print()
# self.print_screen()
print("byte 26 (keyboard):", self.memory[26])
print()
def hlt(self, operand):
self.running = False
def nop(self, operand):
pass
def lda_lit(self, operand):
self.acc = operand
self.flags['Z'] = True if self.acc == 0 else False
self.flags['Eq'] = True if self.acc == operand else False
self.flags['N'] = True if self.acc > 127 else False
def lda_mem(self, operand):
self.acc = self.memory[operand]
self.flags['Z'] = True if self.acc == 0 else False
self.flags['Eq'] = True if self.acc == operand else False
self.flags['N'] = True if self.acc > 127 else False
def sta_lit(self, operand):
self.memory[operand] = self.acc
def sta_mem(self, operand):
self.memory[self.memory[operand]] = self.acc
def add_lit(self, operand):
self.acc = self.acc + operand
if self.acc > 255:
self.acc = self.acc % 256
self.flags['C'] = True
else:
self.flags['C'] = False
self.flags['Z'] = True if self.acc == 0 else False
self.flags['Eq'] = True if self.acc == operand else False
self.flags['N'] = True if self.acc > 127 else False
def add_mem(self, operand):
self.acc = self.acc + self.memory[operand]
if self.acc > 255:
self.acc = self.acc % 256
self.flags['C'] = True
else:
self.flags['C'] = False
self.flags['Z'] = True if self.acc == 0 else False
self.flags['Eq'] = True if self.acc == operand else False
self.flags['N'] = True if self.acc > 127 else False
def sub_lit(self, operand):
self.acc = self.acc - operand
if self.acc < 0:
self.acc = self.acc % 256
self.flags['C'] = True
else:
self.flags['C'] = False
self.flags['Z'] = True if self.acc == 0 else False
self.flags['Eq'] = True if self.acc == operand else False
self.flags['N'] = True if self.acc > 127 else False
def sub_mem(self, operand):
self.acc = self.acc - self.memory[operand]
if self.acc > 255:
self.acc = self.acc % 256
self.flags['C'] = True
else:
self.flags['C'] = False
self.flags['Z'] = True if self.acc == 0 else False
self.flags['Eq'] = True if self.acc == operand else False
self.flags['N'] = True if self.acc > 127 else False
def jmp_lit(self, operand):
self.IP = operand
def jmp_mem(self, operand):
self.IP = self.memory[operand]
def ske(self, operand): # FIXME
# if self.flags['Eq']:
# self.IP += 2
if self.acc == operand:
self.IP += 2
def skz(self, operand):
if self.flags['Z']:
self.IP += 2
def skn(self, operand):
if self.flags['N']:
self.IP += 2
def skc(self, operand):
if self.flags['C']:
self.IP += 2
def cst(self, operand):
self.flags['C'] = True
def ccl(self, operand):
self.flags['C'] = False
nums2mnems = {
0: hlt, # x0
1: nop, # x1
2: lda_lit, # 02
3: sta_lit, # 03
4: add_lit, # 04
5: sub_lit, # 05
6: jmp_lit, # 06
7: ske, # x7
8: skz, # x8
9: skn, # x9
10: skc, # A
11: cst, # B
12: ccl, # C
16: hlt, #
17: nop, #
18: lda_mem, # 12
19: sta_mem, # 13
20: add_mem, # 14
21: sub_mem, # 15
22: jmp_mem, # 16
23: ske,
24: skz,
25: skn,
26: skc,
27: cst,
28: ccl,
}

124
python/simulator-scrap.py Normal file
View File

@ -0,0 +1,124 @@
numericKeys = [ "0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F" ]
def toHex(n):
return "%0.2X" % n
class Monitor:
def __init__(self, cpu):
self.cpu = cpu
self.monitorMode = 'addressEntry' # or dataEntry
self.monitorAddressInput = TwoDigitHexInput()
self.monitorDataInput = TwoDigitHexInput()
# In data entry mode, when a full byte is keyed in,
# the next keypress advances to the next address and continues entering data there.
# This variable tracks whether it's time to do that or not.
self.advanceDataEntryNextPress = False
def handleKeys(self):
keypad_event = keymatrix.events.get()
keyPressed = True if (keypad_event and keypad_event.released ) else False
key = keymap[keypad_event.key_number] if keyPressed else False
numericKeyPressed = True if (keyPressed and (key in numericKeys)) else False
if self.cpu.running:
if key == "runhalt":
print("HALT PRESSED")
self.cpu.running = False
time.sleep(0.5) # lazy debounce
# km.events.clear() # don't track keypresses from during the run
if numericKeyPressed:
self.cpu.memory[26] = int(key, 16)
elif not self.cpu.running:
if key == "runhalt":
self.cpu.running = True
print("\nSTARTING")
time.sleep(0.5) # lazy debounce
if key == "addr":
self.monitorMode = 'addressEntry'
print("\nENTERING", self.monitorMode, "MODE")
self.monitorAddressInput.currentDigit = 0
time.sleep(0.5) # lazy debounce
if key == "data":
self.monitorMode = 'dataEntry'
print("\nENTERING", self.monitorMode, "MODE")
self.monitorDataInput.clear()
self.advanceDataEntryNextPress = False
time.sleep(0.5) # lazy debounce
if key == "step":
print("\nSINGLE STEP FROM MONITOR ADDR")
# self.IP = self.monitorAddressInput.value
self.cpu.step()
time.sleep(0.5) # lazy debounce
if numericKeyPressed:
if self.monitorMode == 'addressEntry':
self.monitorAddressInput.input(int(key, 16))
self.cpu.IP = self.monitorAddressInput.value
print("MA", self.cpu.IP)
if self.monitorMode == 'dataEntry':
if self.advanceDataEntryNextPress:
print("ADVANCING")
self.cpu.IP = (self.cpu.IP + 1) % 256
# self.monitorDataInput.clear() # reset .currentDigit
self.monitorDataInput.set(self.cpu.memory[self.cpu.IP])
self.advanceDataEntryNextPress = False
self.monitorDataInput.input(int(key, 16))
self.cpu.memory[self.cpu.IP] = self.monitorDataInput.value
print("MD", self.monitorDataInput.value)
if self.monitorDataInput.currentDigit == 0: # that was the second keypress, so next keypress is for the next address
self.advanceDataEntryNextPress = True
print("Acc", self.cpu.acc, "IP", self.cpu.IP, "Data", self.cpu.memory[self.cpu.IP], "\n")
def displayScreen(self):
for x in range(8):
for y in range(8):
matrix[x, y] = self.cpu.memory[x + (8*y)]
def run(self):
#self.cpu.start()
t = time.time()
while (time.time() - t) < 120: # TODO: add a time delta or sth maybe so this doesn't just burn cycles
self.handleKeys()
display_1.print(toHex(self.cpu.IP) + toHex(self.cpu.memory[self.cpu.IP]))
# display_1.print(toHex(self.monitorAddressInput.value) + toHex(self.cpu.memory[self.cpu.IP]))
# display_2.print(toHex(self.cpu.IP) + toHex(self.cpu.acc))
display_2.print(toHex(self.cpu.acc))
self.displayScreen()
if self.cpu.running:
self.cpu.step()
# time.sleep(0.5) # TODO ?
print("timeout")
print(self.cpu.memory)
cpu = CPU()
monitor = Monitor(cpu)
# preamble = '00 ' * 64
# prog = preamble + '02 01 13 f0 12 f0 04 02 03 f0 12 f0 05 41 08 00 06 40 00 00' # STRIPES
# offset = 64
# prog = preamble + '02 01 13 f0 12 f0 04 02 03 f0 05 08 09 00 04 09 03 f0 07 41 06' + toHex(offset) + '00 00'
#prog = '00'
# program_bytes = bytearray.fromhex(prog.replace(" ", ""))
# Add jmp at addr 254:
#program_with_jump = program_bytes + bytearray(254 - len(program_bytes)) + bytearray.fromhex('0600') # jump to addr 00
# program_with_jump = program_bytes + bytearray(254 - len(program_bytes)) + bytearray.fromhex('0640') # jump to addr 0x40 (dec 64)
with open('test-multiply2.bin', 'rb') as file:
program_bytes = bytearray(file.read())
cpu.load_memory(program_bytes)
monitor.run()

5
python/simulator.py Normal file
View File

@ -0,0 +1,5 @@
import cpu
c = cpu.CPU()
print(c)

@ -1 +0,0 @@
Subproject commit 1d98a0707c3e61e362d2d3d5413b475437b5de0e