1
0

First commit

This commit is contained in:
Jens True 2024-07-30 11:01:26 +00:00
commit ae059d7b8b
51 changed files with 5065 additions and 0 deletions

5
.gitignore vendored Normal file

@ -0,0 +1,5 @@
.phpdoc/
.phplint.cache/
.phpunit.cache/
output/
vendor/

50
.phpcs.xml Normal file

@ -0,0 +1,50 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="PHP_CodeSniffer" xsi:noNamespaceSchemaLocation="phpcs.xsd">
<description>Coding standard</description>
<file>src/</file>
<rule ref="PSR1">
<exclude name="Generic.Files.LineLength"/>
</rule>
<rule ref="PSR2"></rule>
<rule ref="PSR12"></rule>
<rule ref="Generic">
<exclude name="Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed"/>
<exclude name="Generic.Files.LowercasedFilename.NotFound"/>
<exclude name="Generic.PHP.ClosingPHPTag.NotFound"/>
<exclude name="Generic.Files.EndFileNoNewline.Found"/>
<exclude name="Generic.Files.EndFileNoNewline.Found"/>
<exclude name="Generic.Arrays.DisallowShortArraySyntax.Found"/>
<exclude name="Generic.Functions.OpeningFunctionBraceKernighanRitchie.BraceOnNewLine"/>
<exclude name="Generic.Classes.OpeningBraceSameLine.BraceOnNewLine"/>
<exclude name="Generic.PHP.LowerCaseConstant.Found"/>
<exclude name="Generic.Formatting.SpaceAfterCast"/>
<exclude name="Generic.Formatting.MultipleStatementAlignment.NotSameWarning"/>
<exclude name="Generic.Commenting.DocComment.MissingShort"/>
<exclude name="Generic.NamingConventions.AbstractClassNamePrefix.Missing"/>
<exclude name="Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed"/>
<exclude name="Generic.NamingConventions.InterfaceNameSuffix.Missing"/>
<exclude name="Generic.Commenting.Todo.TaskFound"/>
<exclude name="Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUse"/>
<exclude name="Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed"/>
<exclude name="Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed"/>
<exclude name="Generic.Formatting.SpaceBeforeCast.NoSpace"/>
<exclude name="Generic.CodeAnalysis.UselessOverridingMethod.Found"/>
<exclude name="Squiz.Functions.MultiLineFunctionDeclaration.NewlineBeforeOpenBrace"/>
</rule>
<!-- Ban some functions -->
<rule ref="Generic.PHP.ForbiddenFunctions">
<properties>
<property name="forbiddenFunctions" type="array">
<element key="sizeof" value="count"/>
<element key="delete" value="unset"/>
<element key="print" value="echo"/>
<element key="is_null" value="null"/>
<element key="create_function" value="null"/>
</property>
</properties>
</rule>
</ruleset>

0
CHANGELOG.md Normal file

0
CONTRIBUTING.md Normal file

0
LICENSE.md Normal file

7
README.md Normal file

@ -0,0 +1,7 @@
SIMAVRPHP
==========
What
-----
Implementing an ATmega328p simulator in PHP. Just because.

20
bin/simavr.php Normal file

@ -0,0 +1,20 @@
<?php
require_once("vendor/autoload.php");
use Monolog\Level;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\FirePHPHandler;
// Create the logger
$logger = new Logger('simavrphp');
// Now add some handlers
$logger->pushHandler(new StreamHandler('php://stderr'));
$avr = new SimAVRPHP\AVR($logger);
$mem = file_get_contents($argv[1]);
$avr->setMemory($mem);
while(true){
$avr->step();
}

48
composer.json Normal file

@ -0,0 +1,48 @@
{
"require": {
"php": "^8.3",
"monolog/monolog": "^3.7"
},
"require-dev": {
"vimeo/psalm": "^5.15",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.3",
"phpmetrics/phpmetrics": "^2.8",
"rector/rector": "^1.2"
},
"autoload": {
"psr-4": {
"SimAVRPHP\\": "src/"
}
},
"scripts": {
"test": "phpunit",
"document": "phpDocumentor",
"benchmark": "phpbench run --report=default --output=build-artifact",
"metrics": "vendor/bin/phpmetrics --config=phpmetrics.json",
"lint": [
"phplint",
"phpcs",
"phpmd src/,tests/ text phpmd.ruleset.xml"
],
"analyze": [
"@analyze-phpstan",
"@analyze-psalm",
"@analyze-rector"
],
"analyze-phpstan":"vendor/bin/phpstan analyze --error-format=raw",
"analyze-psalm": "vendor/bin/psalm --no-cache --show-info=true",
"analyze-rector": "vendor/bin/rector --dry-run",
"html": [
"pandoc -s README.md -o output/README.html"
],
"all": [
"@test",
"@lint",
"@analyze",
"@document",
"@metrics",
"@html"
]
}
}

3880
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

BIN
composer.phar Normal file

Binary file not shown.

20
phpdoc.xml Normal file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<phpdocumentor
configVersion="3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://www.phpdoc.org"
xsi:noNamespaceSchemaLocation="https://docs.phpdoc.org/latest/phpdoc.xsd"
>
<title>trueskill</title>
<paths>
<output>output/docs</output>
</paths>
<version number="latest">
<api>
<source>
<path>src</path>
</source>
</api>
</version>
<!--setting name="graphs.enabled" value="true"/-->
</phpdocumentor>

47
phpmd.ruleset.xml Normal file

@ -0,0 +1,47 @@
<?xml version="1.0"?>
<ruleset name="Custom PHPMD rules"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0
http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="
http://pmd.sf.net/ruleset_xml_schema.xsd">
<description>
Custom PHPMD rules
</description>
<rule ref="rulesets/cleancode.xml">
<exclude name="StaticAccess" />
<exclude name="ElseExpression" />
</rule>
<rule ref="rulesets/codesize.xml" >
<exclude name="TooManyMethods" />
<exclude name="TooManyPublicMethods" />
</rule>
<rule ref="rulesets/codesize.xml/TooManyMethods">
<priority>1</priority>
<properties>
<property name="ignorepattern" value="#^(set|get|test)|test$#i" />
</properties>
</rule>
<rule ref="rulesets/codesize.xml/TooManyPublicMethods">
<priority>1</priority>
<properties>
<property name="ignorepattern" value="#^(set|get|test)|test$#i" />
</properties>
</rule>
<rule ref="rulesets/design.xml" />
<rule ref="rulesets/naming.xml" >
<exclude name="LongClassName" />
<exclude name="ShortClassName" />
<exclude name="ShortVariable" />
<exclude name="LongVariable" />
<exclude name="ShortMethodName" />
</rule>
<rule ref="rulesets/unusedcode.xml" />
</ruleset>

21
phpmetrics.json Normal file

@ -0,0 +1,21 @@
{
"composer": true,
"includes": [
"src"
],
"excludes": [
"tests"
],
"report": {
"html": "output/metrics/",
"json": "output/metrics/report.json"
},
"plugins": {
"git": {
"binary": "git"
},
"junit": {
"file": "output/test.xml"
}
}
}

5
phpstan.neon Normal file

@ -0,0 +1,5 @@
parameters:
level: 6
paths:
- src
- tests

24
phpunit.xml Normal file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd" backupGlobals="false" bootstrap="vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" cacheDirectory=".phpunit.cache" backupStaticProperties="false" displayDetailsOnTestsThatTriggerWarnings="true" beStrictAboutCoverageMetadata="true" requireCoverageMetadata="true">
<testsuites>
<testsuite name="Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">src/</directory>
</include>
</source>
<logging>
<junit outputFile="output/test/junit.xml"/>
<testdoxHtml outputFile="output/test/index.html"/>
</logging>
<coverage>
<report>
<html outputDirectory="output/coverage" />
<clover outputFile ="output/coverage/clover.xml" />
</report>
</coverage>
</phpunit>

18
psalm.xml Normal file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<psalm
errorLevel="3"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
findUnusedBaselineEntry="true"
findUnusedCode="false"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
<file name="bin/simavr.php" />
</projectFiles>
</psalm>

18
rector.php Normal file

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\CodeQuality\Rector\ClassMethod\LocallyCalledStaticMethodToNonStaticRector;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/src',
__DIR__ . '/tests',
])
// uncomment to reach your current PHP version
->withPhpSets()
->withPreparedSets(deadCode: true, codeQuality: true, codingStyle: true, typeDeclarations : true, privatization: true, naming: false, instanceOf: true, earlyReturn: true, strictBooleans: true)
->withSkip([
LocallyCalledStaticMethodToNonStaticRector::class
]);;

BIN
resources/blinky/blink.bin Normal file

Binary file not shown.

17
resources/blinky/blink.c Normal file

@ -0,0 +1,17 @@
#ifndef F_CPU
#define F_CPU 1000000UL
#endif
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
DDRB = 0b00000001;
while (1) {
PORTB = 0b00000001;
PORTB = 0b00000000;
}
return 0;
}

@ -0,0 +1,69 @@
blink.elf: file format elf32-avr
Disassembly of section .text:
00000000 <__vectors>:
0: 0c 94 34 00 jmp 0x68 ; 0x68 <__ctors_end>
4: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
8: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
10: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
14: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
18: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
1c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
20: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
24: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
28: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
2c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
30: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
34: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
38: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
3c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
40: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
44: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
48: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
4c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
50: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
54: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
58: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
5c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
60: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
64: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
00000068 <__ctors_end>:
68: 11 24 eor r1, r1
6a: 1f be out 0x3f, r1 ; 63
6c: cf ef ldi r28, 0xFF ; 255
6e: d8 e0 ldi r29, 0x08 ; 8
70: de bf out 0x3e, r29 ; 62
72: cd bf out 0x3d, r28 ; 61
74: 0e 94 40 00 call 0x80 ; 0x80 <main>
78: 0c 94 45 00 jmp 0x8a ; 0x8a <_exit>
0000007c <__bad_interrupt>:
7c: 0c 94 00 00 jmp 0 ; 0x0 <__vectors>
00000080 <main>:
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
DDRB = 0b00000001;
80: 81 e0 ldi r24, 0x01 ; 1
82: 84 b9 out 0x04, r24 ; 4
while (1) {
PORTB = 0b00000001;
84: 85 b9 out 0x05, r24 ; 5
PORTB = 0b00000000;
86: 15 b8 out 0x05, r1 ; 5
88: fd cf rjmp .-6 ; 0x84 <main+0x4>
0000008a <_exit>:
8a: f8 94 cli
0000008c <__stop_program>:
8c: ff cf rjmp .-2 ; 0x8c <__stop_program>

BIN
resources/blinky/blink.elf Executable file

Binary file not shown.

@ -0,0 +1,10 @@
:100000000C9434000C943E000C943E000C943E0082
:100010000C943E000C943E000C943E000C943E0068
:100020000C943E000C943E000C943E000C943E0058
:100030000C943E000C943E000C943E000C943E0048
:100040000C943E000C943E000C943E000C943E0038
:100050000C943E000C943E000C943E000C943E0028
:100060000C943E000C943E0011241FBECFEFD8E04C
:10007000DEBFCDBF0E9440000C9445000C940000F0
:0E00800081E084B985B915B8FDCFF894FFCFA3
:00000001FF

BIN
resources/blinky/blink.o Normal file

Binary file not shown.

7
resources/blinky/compile.sh Executable file

@ -0,0 +1,7 @@
#!/bin/sh
avr-gcc -g -Os -mmcu=atmega328p -c blink.c
avr-gcc -g -mmcu=atmega328p -o blink.elf blink.o
avr-objcopy -j .text -j .data -O ihex blink.elf blink.hex
avr-size --format=avr --mcu=atmega328p blink.elf
objcopy --input-target=ihex --output-target=binary blink.hex blink.bin
avr-objdump -d -S -m avr5 blink.elf > blink.dump

BIN
resources/blinky2/blink.bin Normal file

Binary file not shown.

19
resources/blinky2/blink.c Normal file

@ -0,0 +1,19 @@
#ifndef F_CPU
#define F_CPU 1000000UL
#endif
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
DDRB = 0b00000001;
while (1) {
PORTB = 0b00000001;
_delay_ms(500);
PORTB = 0b00000000;
_delay_ms(500);
}
return 0;
}

@ -0,0 +1,94 @@
blink.elf: file format elf32-avr
Disassembly of section .text:
00000000 <__vectors>:
0: 0c 94 34 00 jmp 0x68 ; 0x68 <__ctors_end>
4: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
8: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
10: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
14: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
18: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
1c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
20: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
24: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
28: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
2c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
30: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
34: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
38: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
3c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
40: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
44: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
48: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
4c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
50: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
54: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
58: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
5c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
60: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
64: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
00000068 <__ctors_end>:
68: 11 24 eor r1, r1
6a: 1f be out 0x3f, r1 ; 63
6c: cf ef ldi r28, 0xFF ; 255
6e: d8 e0 ldi r29, 0x08 ; 8
70: de bf out 0x3e, r29 ; 62
72: cd bf out 0x3d, r28 ; 61
74: 0e 94 40 00 call 0x80 ; 0x80 <main>
78: 0c 94 57 00 jmp 0xae ; 0xae <_exit>
0000007c <__bad_interrupt>:
7c: 0c 94 00 00 jmp 0 ; 0x0 <__vectors>
00000080 <main>:
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
DDRB = 0b00000001;
80: 81 e0 ldi r24, 0x01 ; 1
82: 84 b9 out 0x04, r24 ; 4
while (1) {
PORTB = 0b00000001;
84: 85 b9 out 0x05, r24 ; 5
#else
//round up by default
__ticks_dc = (uint32_t)(ceil(fabs(__tmp)));
#endif
__builtin_avr_delay_cycles(__ticks_dc);
86: 2f e9 ldi r18, 0x9F ; 159
88: 36 e8 ldi r19, 0x86 ; 134
8a: 91 e0 ldi r25, 0x01 ; 1
8c: 21 50 subi r18, 0x01 ; 1
8e: 30 40 sbci r19, 0x00 ; 0
90: 90 40 sbci r25, 0x00 ; 0
92: e1 f7 brne .-8 ; 0x8c <main+0xc>
94: 00 c0 rjmp .+0 ; 0x96 <main+0x16>
96: 00 00 nop
_delay_ms(500);
PORTB = 0b00000000;
98: 15 b8 out 0x05, r1 ; 5
9a: 2f e9 ldi r18, 0x9F ; 159
9c: 36 e8 ldi r19, 0x86 ; 134
9e: 91 e0 ldi r25, 0x01 ; 1
a0: 21 50 subi r18, 0x01 ; 1
a2: 30 40 sbci r19, 0x00 ; 0
a4: 90 40 sbci r25, 0x00 ; 0
a6: e1 f7 brne .-8 ; 0xa0 <main+0x20>
a8: 00 c0 rjmp .+0 ; 0xaa <main+0x2a>
aa: 00 00 nop
ac: eb cf rjmp .-42 ; 0x84 <main+0x4>
000000ae <_exit>:
ae: f8 94 cli
000000b0 <__stop_program>:
b0: ff cf rjmp .-2 ; 0xb0 <__stop_program>

BIN
resources/blinky2/blink.elf Executable file

Binary file not shown.

@ -0,0 +1,13 @@
:100000000C9434000C943E000C943E000C943E0082
:100010000C943E000C943E000C943E000C943E0068
:100020000C943E000C943E000C943E000C943E0058
:100030000C943E000C943E000C943E000C943E0048
:100040000C943E000C943E000C943E000C943E0038
:100050000C943E000C943E000C943E000C943E0028
:100060000C943E000C943E0011241FBECFEFD8E04C
:10007000DEBFCDBF0E9440000C9457000C940000DE
:1000800081E084B985B92FE936E891E0215030400C
:100090009040E1F700C0000015B82FE936E891E084
:1000A000215030409040E1F700C00000EBCFF894C1
:0200B000FFCF80
:00000001FF

BIN
resources/blinky2/blink.o Normal file

Binary file not shown.

7
resources/blinky2/compile.sh Executable file

@ -0,0 +1,7 @@
#!/bin/sh
avr-gcc -g -Os -mmcu=atmega328p -c blink.c
avr-gcc -g -mmcu=atmega328p -o blink.elf blink.o
avr-objcopy -j .text -j .data -O ihex blink.elf blink.hex
avr-size --format=avr --mcu=atmega328p blink.elf
avr-objcopy --input-target=ihex --output-target=binary blink.hex blink.bin
avr-objdump -d -S -m avr5 blink.elf > blink.dump

59
src/AVR.php Normal file

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP;
use SimAVRPHP\Exceptions\InvalidAddressException;
use Psr\Log\LoggerInterface;
class AVR
{
public ProgramCounter $pc;
public Registers $registers;
/**
* @var Stack Contains the CPU stack
*/
public Stack $stack;
public string $memory = "";
public function __construct(public LoggerInterface $logger)
{
$this->pc = new ProgramCounter();
$this->registers = new Registers();
$this->stack = new Stack();
$this->reset();
}
public function setMemory(string $memory): void
{
$this->memory = $memory;
}
public function reset(): void
{
$this->pc->reset();
}
public function step(): void
{
$rawOperation = $this->getOperation();
$operation = Opcode\Instruction::createInstructionFromOpcode($this, $rawOperation);
$this->logger->debug("Prepare instruction for execution", ['PC' => $this->pc->get(), 'Class' => $operation::class]);
$operation->execute();
}
private function getOperation(): string
{
$opcode = unpack("v2", $this->memory, $this->pc->get());
if ($opcode == FALSE) {
throw new InvalidAddressException();
}
return str_pad(decbin((int)$opcode[1]), 16, "0", STR_PAD_LEFT) . str_pad(decbin((int)$opcode[2]), 16, "0", STR_PAD_LEFT);
}
}

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP\Exceptions;
use Exception;
class InvalidAddressException extends Exception
{
}

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP\Exceptions;
use Exception;
class UnsupportedInstructionException extends Exception
{
}

29
src/Opcode/BRNE.php Normal file

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP\Opcode;
class BRNE extends Instruction
{
public const string OPCODE = "111101kkkkkkk001";
public const int WORDS = 1;
public function do(int $k): void
{
$k = Instruction::signed($k, -64, 63);
$this->debug('Branch if Not Equal: ' . $k);
$return_address = $this->mcu->pc->get() + (self::WORDS << 1);
$this->mcu->stack->push($return_address);
$newPC = $this->mcu->pc->get();
if ($this->mcu->registers->getZ()) {
$newPC += (self::WORDS << 1);
} else {
$newPC += ($k << 1) + 2;
}
$this->mcu->pc->jump($newPC);
}
}

22
src/Opcode/CALL.php Normal file

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP\Opcode;
class CALL extends Instruction
{
public const string OPCODE = "1001010kkkkk111kkkkkkkkkkkkkkkkk";
public const int WORDS = 2;
public const int CYCLES = 4;
public function do(int $k): void
{
$this->debug("Call", ['k' => $k]);
$return_address = $this->mcu->pc->get() + (self::WORDS << 1);
$this->mcu->stack->push($return_address);
$this->mcu->pc->jump($k << 1);
}
}

19
src/Opcode/CLI.php Normal file

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP\Opcode;
class CLI extends Instruction
{
public const string OPCODE = "1001010011111000";
public const int WORDS = 1;
public const int CYCLES = 1;
public function do(): void
{
$this->mcu->registers->setI(FALSE);
}
}

26
src/Opcode/EOR.php Normal file

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP\Opcode;
class EOR extends Instruction
{
public const string OPCODE = "001001rdddddrrrr";
public const int WORDS = 1;
public const int CYCLES = 1;
public function do(int $r, int $d): void
{
$Rd = $this->mcu->registers->get($d);
$Rr = $this->mcu->registers->get($r);
$result = $Rd ^ $Rr;
$this->mcu->registers->set($d, $result);
$this->debug(sprintf('Exclusive OR: %d xor %d = %d', $Rd, $Rr, $result));
$this->mcu->pc->stepWords(self::WORDS);
}
}

117
src/Opcode/Instruction.php Normal file

@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP\Opcode;
use SimAVRPHP\AVR;
use SimAVRPHP\Exceptions\UnsupportedInstructionException;
use Exception;
class Instruction
{
/**
* @var class-string[]
*/
protected static $AVRInstructions = [
\SimAVRPHP\Opcode\BRNE::class,
\SimAVRPHP\Opcode\CALL::class,
\SimAVRPHP\Opcode\CLI::class,
\SimAVRPHP\Opcode\EOR::class,
\SimAVRPHP\Opcode\JMP::class,
\SimAVRPHP\Opcode\LDI::class,
\SimAVRPHP\Opcode\NOP::class,
\SimAVRPHP\Opcode\OUT::class,
\SimAVRPHP\Opcode\RJMP::class,
\SimAVRPHP\Opcode\SBCI::class,
\SimAVRPHP\Opcode\SUBI::class,
];
/**
* @param array<string,int> $operands
*/
protected function __construct(protected AVR $mcu, protected array $operands)
{
}
/**
* @return bool|array<string,int>
*/
private static function decode(string $opcode, string $pattern): array|bool
{
$data = [];
$pattern_length = strlen($pattern);
if ($pattern_length != 16 && $pattern_length != 32) {
throw new Exception('Pattern length invalid ' . $pattern_length);
}
for ($c = 0; $c < $pattern_length; $c++) {
$token = $pattern[$c];
$instructionBit = $opcode[$c];
if ($token === "1" || $token === "0") {
if ($instructionBit !== $token) {
return FALSE;
}
} else {
if (! isset($data[$token])) {
$data[$token] = "";
}
$data[$token] .= $instructionBit;
}
}
return array_map(
fn(string $element): int => (int)bindec($element),
$data
);
}
public function execute(): void
{
if (is_callable([$this,'do'])) {
call_user_func_array([$this,'do'], $this->operands);
}
}
public static function createInstructionFromOpcode(AVR $mcu, string $opcode): Instruction
{
foreach (self::$AVRInstructions as $i) {
$data = self::decode($opcode, constant($i . "::OPCODE"));
if ($data !== FALSE) {
$obj = new $i($mcu, $data);
if ($obj instanceof Instruction) {
return $obj;
}
}
}
throw new UnsupportedInstructionException("Unsupported instruction", (int)bindec($opcode));
}
/**
* @param array<string,int> $param
*/
public function debug(string $string, array $param = []): void
{
$this->mcu->logger->debug($string, $param);
}
protected function getRegister(int $reg): int
{
return $this->mcu->registers->get($reg);
}
public static function signed(int $v, int $min, int $max): int
{
if ($v > $max + abs($min)) {
throw new Exception("Not valid within range");
}
if ($v > $max) {
return $min - 1 + $v - $max;
}
return $v;
}
}

20
src/Opcode/JMP.php Normal file

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP\Opcode;
class JMP extends Instruction
{
public const string OPCODE = "1001010kkkkk110kkkkkkkkkkkkkkkkk";
public const int WORDS = 2;
public const int CYCLES = 3;
public function do(int $k): void
{
$this->debug("Jump to: " . $k);
$this->mcu->pc->jump($k << 1);
}
}

23
src/Opcode/LDI.php Normal file

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP\Opcode;
class LDI extends Instruction
{
public const string OPCODE = "1110KKKKddddKKKK";
public const int WORDS = 1;
public const int CYCLES = 1;
public function do(int $K, int $d): void
{
$reg = $d + 16;
$this->mcu->registers->set($reg, $K);
$this->debug(sprintf('Load Immediate: %d to R%d', $K, $reg));
$this->mcu->pc->stepWords(self::WORDS);
}
}

19
src/Opcode/NOP.php Normal file

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP\Opcode;
class NOP extends Instruction
{
public const string OPCODE = "0000000000000000";
public const int WORDS = 1;
public const int CYCLES = 1;
public function do(): void
{
$this->mcu->pc->stepWords(self::WORDS);
}
}

24
src/Opcode/OUT.php Normal file

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP\Opcode;
class OUT extends Instruction
{
public const string OPCODE = "10111AArrrrrAAAA";
public const int WORDS = 1;
public const int CYCLES = 1;
public function do(int $r, int $A): void
{
$Rr = $this->mcu->registers->get($r);
$this->debug(sprintf('OUT: R%d Value %d to A: %d', $r, $Rr, $A));
$this->mcu->registers->setA($A, $Rr);
$this->mcu->pc->stepWords(self::WORDS);
}
}

23
src/Opcode/RJMP.php Normal file

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP\Opcode;
class RJMP extends Instruction
{
public const string OPCODE = "1100kkkkkkkkkkkk";
public const int WORDS = 1;
public const int CYCLES = 2;
public function do(int $k): void
{
$k = $this->signed($k, -2048, 2047);
$new_pc = $this->mcu->pc->get() + ($k * 2) + 2;
$this->debug("Relative Jump to: " . sprintf("0x%X", $new_pc));
$this->mcu->pc->jump($new_pc);
}
}

25
src/Opcode/SBCI.php Normal file

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP\Opcode;
class SBCI extends Instruction
{
public const string OPCODE = "0100KKKKddddKKKK";
public const int WORDS = 1;
public const int CYCLES = 1;
public function do(int $K, int $d): void
{
$reg = $d + 16;
$value = $this->mcu->registers->get($reg);
$result = $value - $K;
$this->mcu->registers->set($reg, $result);
$this->debug("Subtract Immediate with Carry SBI : R" . $reg . " - " . $K);
$this->mcu->pc->stepWords(self::WORDS);
}
}

25
src/Opcode/SUBI.php Normal file

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP\Opcode;
class SUBI extends Instruction
{
public const string OPCODE = "0101KKKKddddKKKK";
public const int WORDS = 1;
public const int CYCLES = 1;
public function do(int $K, int $d): void
{
$reg = $d + 16;
$value = $this->mcu->registers->get($reg);
$result = $value - $K;
$this->mcu->registers->set($reg, $result);
$this->debug("Subtract Immediate: R" . $reg . " - " . $K);
$this->mcu->pc->stepWords(self::WORDS);
}
}

44
src/ProgramCounter.php Normal file

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP;
use SimAVRPHP\Exceptions\InvalidAddressException;
class ProgramCounter
{
private const int RESET_PC_VALUE = 0;
private int $programCounter = self::RESET_PC_VALUE;
public function get(): int
{
return $this->programCounter;
}
public function jump(int $address): void
{
if (($address % 2) != 0) {
throw new InvalidAddressException("Invalid address", $address);
}
$this->programCounter = $address;
}
public function relativeJump(int $address): void
{
$this->jump($this->programCounter + $address);
}
public function stepWords(int $word): int
{
$this->programCounter += ($word << 1);
return $this->programCounter;
}
public function reset(): void
{
$this->programCounter = self::RESET_PC_VALUE;
}
}

69
src/Registers.php Normal file

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP;
class Registers
{
private bool $Z = FALSE;
private bool $I = FALSE;
/**
* @var int[]
*/
private $registers = [];
/**
* @var int[]
*/
private $A = [];
public function __construct()
{
$this->reset();
}
public function reset(): void
{
$this->Z = FALSE;
$this->I = FALSE;
$this->registers = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
}
public function get(int $i): int
{
return $this->registers[$i];
}
public function set(int $i, int $value): void
{
$this->registers[$i] = $value;
}
public function setA(int $A, int $value): void
{
$this->A[$A] = $value;
}
public function getA(int $A): int
{
return $this->A[$A];
}
public function getZ(): bool
{
return $this->Z;
}
public function setI(bool $I): void
{
$this->$I = $I;
}
public function getI(): bool
{
return $this->I;
}
}

34
src/Stack.php Normal file

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP;
use Exception;
class Stack
{
/**
* @var int[]
*/
private array $stack = [];
public function reset(): void
{
$this->stack = [];
}
public function push(int $value): void
{
$this->stack[] = $value;
}
public function pop(): int
{
if (count($this->stack) >= 1) {
return array_pop($this->stack);
}
throw new Exception("Stack Underflow");
}
}

26
tests/AVRTest.php Normal file

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP\Tests;
use SimAVRPHP\AVR;
use Psr\Log\NullLogger;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
#[CoversClass(AVR::class)]
class AVRTest extends TestCase
{
public function testStepNOP(): void
{
$avr = new AVR(new NullLogger);
$avr->setMemory("\00\00\00\00");
$avr->step();
}
}

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace SimAVRPHP\Tests;
use SimAVRPHP\ProgramCounter;
use SimAVRPHP\Exceptions\InvalidAddressException;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
#[CoversClass(ProgramCounter::class)]
class ProgramCounterTest extends TestCase
{
public function testStepAndSet(): void
{
$pc = new ProgramCounter();
$this->assertEquals(0, $pc->get());
$pc->stepWords(1); //Step 1 word
$this->assertEquals(2, $pc->get());
$pc->jump(0x100);
$this->assertEquals(0x100, $pc->get());
$pc->reset();
$this->assertEquals(0, $pc->get());
}
public function testSetinvalid(): void
{
$pc = new ProgramCounter();
$this->expectException(InvalidAddressException::class);
$pc->jump(3); //Uneven address
}
}