py_register_machine2.core

Parts

py_register_machine2.core.parts: Basic parts of the register machine

exception py_register_machine2.core.parts.AddressError(*args)[source]
raised by a device if the requested offset exceeds the size of the device
class py_register_machine2.core.parts.BUS(width=64, debug=0)[source]

The BUS object

A BUS object connects the Processor with one or more Memory alike operating devices (WordDevice).

Before the BUS starts working, devices can be registered, the address spaces of the devices are organized incremental:

d1 = WordDevice(4)
d2 = WordDevice(5)
d3 = WordDevice(19)

b = BUS()
addr1 = b.register_device(d1)
addr2 = b.register_device(d2)
addr3 = b.register_device(d3)

print( (addr1, addr2, addr3))
# (0, 4, 9)

Once the BUS started working (a read/write operation has been used) BUS.register_device will raise a BUSSetupError. If the addresspace of the BUS is too small do hold a new device, BUS.register_device will raise a BUSSetupError.

The number of read/write actions can be observed by accessing the variables reads and writes

read_word(offset)[source]

Read one word from a device. The offset is device_addr + device_offset, e.g.:

offset = 3 # third word of the device
offset += addr2
b.read_word(offset)
# reads third word of d2.

Truncates the value according to width.

May raise BUSError, if the offset exceeds the address space.

register_device(word_device)[source]

Register the WordDevice word_device in the bus returns the start address of the device.

raises: BUSSetupError, if the device cannot be registered.

write_word(offset, word)[source]

Writes one word from a device, see read_word.

exception py_register_machine2.core.parts.BUSError(*args)[source]
raised by a BUS if an operation failed.
exception py_register_machine2.core.parts.BUSSetupError(*args)[source]
raised by a BUS if the setup failed.
class py_register_machine2.core.parts.Integer(value=0, width=64)[source]

The register machine may have a special width. This is handled by the Integer objects.

Automatically truncates the value to the defined width.

Use setvalue and getvalue or setuvalue and getuvalue to access the value.

Uses a bitset internally.

getuvalue()[source]

Get the unsigned value of the Integer, truncate it and handle Overflows.

getvalue()[source]

Get the signed value of the Integer, truncate it and handle Overflows.

setuvalue(value)[source]

Set the unsigned value of the Integer.

setvalue(value)[source]

Set the signed value of the Integer.

exception py_register_machine2.core.parts.ReadOnlyError(*args)[source]
raised by a device if it is read-only
class py_register_machine2.core.parts.Register(name, width=64)[source]

Basically hold one value and permitt read/write operations. There may be several subclasses, like Input/Output Register.

The name will be used by the assembler.

read()[source]

Return the content of the Register, may execute a function

write(value)[source]

Set the content of the Register, may execute a function

class py_register_machine2.core.parts.WordDevice(size, width=64, mode=3, debug=0)[source]

Base Device for the register machine. The words have the width width and are stored in an Integer object.

Values are accessed by read and write

read(offset)[source]

Returns the value of the memory word at offset.

Might raise WriteOnlyError, if the device is write-only. Might raise AddressError, if the offset exceeds the size of the device.

write(offset, value)[source]

Writes the memory word at offset to value.

Might raise ReadOnlyError, if the device is read-only. Might raise AddressError, if the offset exceeds the size of the device.

exception py_register_machine2.core.parts.WriteOnlyError(*args)[source]
raised by a device if it is write-only

Memory

class py_register_machine2.core.memory.BUS(width=64, debug=0)[source]

The processor’s memory BUS, its devices are ROM and RAM.

class py_register_machine2.core.memory.RAM(size, width=64, debug=0)[source]

The Random Access Memory Device

By default the RAM device is filled with zeros. After poweron the Bootcode in the ROM might perform read/write operations on the RAM. If the register machine is to execute programs from the Flash device, this code has to be copied into the RAM.

class py_register_machine2.core.memory.ROM(size, width=64, debug=0)[source]

The Read Only Memory Device

of the register machine stores either the boot code (for big programs) or the complete program, if the program is really small.

The ROM is attached to the same BUS as the RAM and always includes the offset 0. The Program Counter (PC) of the Processor points to this word on powerup.

Because RAM and ROM are in the same address space the following formula defines the size of RAM and ROM:

addr_space = 2 ** memorybus.width
ram.size + rom.size < addr_space

A write call will raise ReadOnlyError.

program(prog, offset=0)[source]

Write the content of the iterable prog starting with the optional offset offset to the device.

Invokes program_word.

program_word(offset, word)[source]

Write the word word to the memory at offset offset. Used to write the boot code.

Might raise AddressError, if the offset exceeds the address space.

Devices

py_register_machine2.core.device: Device BUS and attached devices

class py_register_machine2.core.device.BUS(width=64, debug=0)[source]

The processor’s device BUS, usually the Flash is attached to this BUS, but there might be more devices.

class py_register_machine2.core.device.Flash(size, width=64, debug=0)[source]

The Program Flash

If the size of the program exceeds the size of the ROM the program has to be written into the Flash. The Flash is a Read/Write device and contains

  • The Program
  • Constants
  • Static Variables

The Flash is a WordDevice and attached to the device.BUS .

program(prog, offset=0)[source]

Write the content of the iterable prog starting with the optional offset offset to the device.

Invokes program_word.

program_word(offset, word)[source]

Program one word of the Flash device. Might raise AddressError.

Register

py_register_machine2.core.register: Registers for the register machine

class py_register_machine2.core.register.BStreamIORegister(name, open_stream_in, open_stream_out, width=64)[source]

Works like StreamIORegister, but open_stream_in and open_stream_out are byte streams (like open("fname", "rb")).

  • A read operation will read width // 8 bytes and convert them to one int.
  • A write operation will write width // 8 bytes
read()[source]

Reads enough bytes from open_stream_in to fill the width (if available) and converts them to an int. Returns this int.

write(word)[source]

Converts the int word to a bytes object and writes them to open_stream_out.

See int_to_bytes.

class py_register_machine2.core.register.OutputRegister(name, open_stream, width=64)[source]

Used to print data to the user. The write call will convert word using chr and write the resulting str to open_stream.

The read call will return the last written word.

open_stream might be a file (like sys.stdout) or an io.StringIO object.

write(word)[source]

Write the chr representation of word to the open_stream.

If chr(word) fails due OverflowError, a "?" will be written.

class py_register_machine2.core.register.Register(name, width=64)[source]

The basic standard register. Permitts read and write, does not execute any callbacks on read/write.

See also: Register

class py_register_machine2.core.register.StreamIORegister(name, open_stream_in, open_stream_out, width=64)[source]

Input/Output Register via streams.

The open_stream_in has to be readable, open_stream_out writeable.

read()[source]

Read a str from open_stream_in and convert it to an integer using ord. The result will be truncated according to Integer.

write(word)[source]

Works like SOwrite.

Processor

class py_register_machine2.core.processor.EnigneControlBits[source]

Container for the static engine controll bits. Used by the Processor to handle his ECR.

class py_register_machine2.core.processor.Processor(f_cpu=None, width=64, interrupts=False, clock_barrier=None, debug=0)[source]

Fetches Opcodes from the ROM or RAM, decodes them and executes the commands.

Phases in one operation cycle:

Fetch Phase
The Processor fetches the Opcode (one word) from the ROM or RAM device according to the program counter and increases the program counter.
Decode Phase
The Processor looks up the Command to execute
Fetch Operands Phase
(optional) If requested the processor fetches the operands and increases the program counter.
Execute Phase
The Processor executes the Command.
Write Back Phase
(optional) If there is a result this result is written to a register or the RAM or a device.

Special Register

  1. The first Register (index 0) is the Program Counter(PC).
  2. The second Register (index 1) is the Engine Control Register (ECR) take a look at EnigneControlBits.
  3. The third Register (index 2) is the Stack Pointer (SP) and may be used for call, ret, push and pop

Internal Constants

Constants used by the Assembler, should be set using setup_done

ROMEND_LOW
First word of the ROM (always 0)
ROMEND_HIGH
Last word of the ROM
RAMEND_LOW
First word of the RAM, (ROMEND_HIGH + rom.size)
RAMEND_HIGH
Last word of the RAM
FLASH_START
First word of the Flash_(always 0)
FLASH_END
Last word of the Flash
Interrupt Name
Address of the interrupt (set by invoking add_interrupt)

Cycles

The number of cycles can be observed by acessing the cycles variable.

add_interrupt(interrupt)[source]

Adds the interrupt to the internal interrupt storage self.interrupts and registers the interrupt address in the internal constants.

add_register(register)[source]

Adds a new register in the RegisterInterface.

Invokes add_register.

do_cycle()[source]

Run one clock cycle of the Processor, works according to processor_phases.

Then all on_cycle_callbacks are executed and the internal Registers are updated.

If f_cpu is set and the execution took not long enough, do_cycle will wait until the right time for the next cycle.

If clock_barrier is set, do_cycle will perform the clock_barrier.wait().

Might raise SIGILL, if there is an invalid opcode.

en_dis_able_interrupts(mask)[source]

This callback might be used by a Register to enable/disable Interrupts.

mask is an int, the Interrupts are bits in this mask, the first registered interrupt has the bit (1 << 0), the n-th Interrupt the bit (1 << (n - 1)). If the bit is cleared (0) the Interrupt will be disabled.

interrupt(address)[source]

Interrupts the Processor and forces him to jump to address. If push_pc is enabled this will push the PC to the stack.

register_command(command)[source]

Register a Command in the Processor, the Command can now be executed by the Processor.

register_device(device)[source]

Registers a device in the device BUS.

Invokes register_device.

register_memory_device(device)[source]

Registers a device in the memory BUS.

Invokes register_device.

register_on_cycle_callback(callback)[source]

A on cycle callback is executed in every clock cycle of the Processor. No on cycle callback modifies the state of the Processor directly, but it might cause an Interrupt.

The on cycle callback is a function without arguments:

def on_cycle_callback():
        print("One cycle done")

The return value of a callback is ignored and the callback must not raise Exceptions, but fatal Errors may stop the engine.

reset()[source]

Resets the control registers of the Processor_(PC, ECR and SP)

run()[source]

Runs do_cycle, until either a stop bit in the ECR is set (see EnigneControlBits), or if an Exception in do_cycle occurs.

setup_done()[source]

Finish the setup of the Processor.

This should be the last call before the Processor is used. Sets the internal constants (used by the assembler) and sets the Stack Pointer to RAMEND_HIGH, if there is a RAM attached. If there is no RAM attached, SP will stay 0.

If there is a RAM attached push_pc is set.

Might raise SetupError.

class py_register_machine2.core.processor.RegisterInterface(registers=[], debug=0, width=64)[source]

Used by the Processor to perform read/write operations on the registers.

add_register(register)[source]

Adds the Register register to the interface.

Will raise a SetupError if the interface is locked (because it is running) or if there is already a Register with the name of the new Register or if the number of Registers would exceed the size of the interface.

Returns the index of the new Register

read(name_or_index)[source]

Read a word from the Register with the name name_or_index or with the index name_or_index. name_or_index hat to be either str or int. If the type of name_or_index is wrong an AttributeError will be raised.

If there is no Register with the specified name or index, a NameError will be raised.

write(name_or_index, word)[source]

Write a word in the Register with the name name_or_index or with the index name_or_index. name_or_index hat to be either str or int. If the type of name_or_index is wrong an AttributeError will be raised.

If there is no Register with the specified name or index, a NameError will be raised.

exception py_register_machine2.core.processor.SIGILL(*args)[source]

Raised if an invalid opcode occurs.

exception py_register_machine2.core.processor.SetupError(*args)[source]

Raised if the setup is invalid.

Commands

py_register_machine2.core.commands: Abstract Commands

class py_register_machine2.core.commands.ArgumentType(type_='register', can_default=False, default=0)[source]

Represents argument types. Actually there are only the types "register" and "const" but it might be helpful to use default values. Those are stored in the ArgumentType, too.

This module provides the functions registerargument() and constargument() those will return an ArgumentType(type_ = "register", can_default = False, default = 0) and an ArgumentType(type_ = "const", can_default = False, default = 0)

class py_register_machine2.core.commands.ArithmeticCommand(mnemonic, opcode, function)[source]

Used for calculation commands, numargs is always 2, both arguments are Registers.

Example: The add command:

add_function = lambda a,b: a+b
add_command = ArithmeticCommand("add", 2, add_function)
exec(operand1, operand2)[source]

Uses two operands and performs a function on their content.:

operand1 = function(operand1, operand2)
class py_register_machine2.core.commands.BaseCommand(mnemonic, opcode, numargs, argtypes)[source]

The base class for Commands.

Every Command has to be derived from BaseCommand and provide the following functions:

argtypes must be a list of ArgumentType objects.

argtypes()[source]

Return a list of ArgumentType objects defining the argument types, i.e.:

[ArgumentType(type_ = "register", can_default = False, default = 0), ArgumentType(type_ = "register", can_default = False, default = 0)]
exec(*args)[source]

Exec will execute the Action of the Command. The method will be provided with numargs arguments and might read/write data via the attributes register_interface, membus and devbus provided once the Command is registered in the Processor using register_command.

mnemonic()[source]

Returns the mnemonic of the command (str). Used by the Assembler and Disassembler.

numargs()[source]

Returns the number of needed arguments. Used in the fetch-operands-phase.

opcode()[source]

Returns the opcode of the command (int). Used by the Assembler and in the decode-command-phase.

class py_register_machine2.core.commands.FunctionCommand(mnemonic, opcode, numargs, function, argtypes)[source]

Provides a basic handle to create Commands.

The argument function is a function with at least three arguments:

  1. register_interface
  2. memory_BUS
  3. device_BUS

The function will be able to access the Processor’s RegisterInterface and BUS es through this arguments.

If the function needs any operands the number of additional arguments have to be in numargs

For arithmetic commands (like add, mul,...) see ArithmeticCommand.

Example: ld Command:

def ld_function(register_interface, memory_BUS, device_BUS, addr_from, to):
        from_ = register_interface.read(addr_from)

        word = memory_BUS.read_word(from_)
        register_interface.write(to, word)

ld_command = FunctionCommand("ld", 34, 2, ld_function, [constargument(), registerargument()])

Example: nop Command:

def nop_function(register_interface, memory_BUS, device_BUS):
        return
nop_command = FunctionCommand("nop", 36, 0, nop_function, [])

Interrupts

py_register_machine2.core.interrupts: Basic module to provide Interrupts.

class py_register_machine2.core.interrupts.Autoreset(name, processor, overflow_size)[source]

A really rude form of the Watchdog. This Interrupt will force the Processor to jump to offset 0.

class py_register_machine2.core.interrupts.Counter(address, name, processor, overflow_size)[source]

A Counter/Timer implementation.

The __init__ method will inject an on_cycle_callback into the Processor. This callback will increment the internal counter variable by one. If the internal counter reaches a predefined value the interrupt method will be invoked.

class py_register_machine2.core.interrupts.Interrupt(address, name, processor)[source]

The Base Class for Interrupts. If Interrupt.interrupt is invoked this will invoke Processor.interrupt and provide the address of the Interrupt.

This will allow one to place an ISR (Interrupt Service Routine) at this address.

interrupt()[source]

Will interrupt the Processor, if the Interrupt is enabled.