diff --git a/README.md b/README.md index 9d6c422..e51e1c0 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,15 @@ Everything can be chained together, or be asserted in one line: Notice the difference: `scratchpads()` will accept an array, and `contains()` will accept the same length in values. You can set the scratchpad/output port/... index as an `int` (0, 1, ...) or as a `str` in hex values ("0", "1F", "FF", ...) +**Assertion output**: if for instance the output ports do not match, the following message will appear in your test output window: + +> AssertionError: Output dos not contain expected values: +output ports_out 3 should contain 0 (hex: 00) but instead contains 12 (hex: 0C) + +Since it's possible to assert hex values (as a `str`) or number values (as an `int`), the test output message always shows both. + +It is possible to write `assert_that.ports([1]).contains(["0A"])` and `assert_that.ports([1]).contains([10])`. Both assert the same thing and contain the same expectation values. + #### Testing only one procedure If you don't want the whole thing to be executed, you can still use opbtest, and call `testproc(name)`: @@ -143,3 +152,4 @@ For instance, `.setregs({"s5": 2, "s6": 3})` will preload register s5 with value Before calling `execute()`, you can preload input port values using `mockinput()`. For instance, `.mockinput(0, 4)` will preload input port 0 with value 4. Psm statements like `input s0, 0` will load 4 into register s4. opbtest acutally replaces the statement with `load s0, 4`, so no actual input statements will be processed. + diff --git a/examples/randompermutations.psm4 b/examples/randompermutations.psm4 new file mode 100644 index 0000000..e76f55f --- /dev/null +++ b/examples/randompermutations.psm4 @@ -0,0 +1,154 @@ +constant QuitPort, FF +constant ConfigA, 01 +constant ConfigB, 02 +constant ConfigC, 03 +constant ConfigD, 04 + +namereg sF, SP ; sF is stack pointer +namereg sA, seed + +init: + load SP, 00 ; 3F = einde. + jump main + + use_random8(random, seed) + +proc random_range(s0 is max, s2 is shifter, s3 is counter) { + if(s0 == 0) { + load seed, 0 + jump done + } +randomizing: + load counter, 0 + load shifter, max + call random +shifting: + ; shift totdat we een 1 tegenkomen (wordt ook nog geshift) + add counter, 1 + sl0 shifter + jump NC, shifting + sub counter, 1 + + ; shift gegenereerd getal zelfde aantal keren, indien te groot: herbegin + for(shifter := 0, shifter < counter, shifter := shifter + 1) { + sl0 seed + } + for(shifter := 0, shifter < counter, shifter := shifter + 1) { + sr0 seed + } + + if(seed > max) { + jump randomizing + } + +done: + ; resultaat - links geshift met 0en (en terug rechts) - zit in seed +} + +proc permutation_to_configuration(s4 is index, s5 is scratchindex, s6 is counter, s7 is configa, s8 is configb) { + load configa, 0 + load configb, 0 + + for(counter := 3, signed(counter >= 0), counter := counter - 1) { + load SP, counter + expr(SP := SP + scratchindex) + fetch index, (SP) + + ; 0000 00 weg; laatse 2 bits relevant tot nu toe + sl0 index + sl0 index + sl0 index + sl0 index + sl0 index + sl0 index + + sl0 index + sla configa + + sl0 index + sla configb + + add SP, 01 + } +} + +proc pg_to_permutation(s6 is counter, s7 is pgcounter, s8 is destcounter, s4 is value, s5 is pgvalue) { + load pgcounter, 10 + + for(counter := 0, counter < 4, counter := counter + 1) { + load SP, counter + fetch value, (SP) + load SP, pgcounter + fetch pgvalue, (SP) + + expr(destcounter := value + 32) + load SP, destcounter + store pgvalue, (SP) + + add pgcounter, 01 + } +} + +main: + load seed, 5A ; replace with LFSA hw lookup + + vars(s0 is random_range_max, s6 is counter, s4 is index, s5 is random_index) + + ; initiele array: [0, 1, 2] in scratchpad 00 -> 03 + for(counter := 0, counter < 4, counter := counter + 1) { + store counter, (SP) + add SP, 01 + } + load SP, 10 + ; initiele p/g: [0, 2, 2, 1] in scratchpad 10 -> 13 (hex) + load counter, 0 + store counter, (SP) + add SP, 01 + load counter, 2 + store counter, (SP) + add SP, 01 + load counter, 2 + store counter, (SP) + add SP, 01 + load counter, 1 + store counter, (SP) + add SP, 01 + + ; backwards Knuth shuffle + for(counter := 3, signed(counter >= 0), counter := counter - 1) { + ; 0) random index in seed + load random_range_max, counter + call random_range + + ; 1) fetch arr[counter] (starting at end) + load SP, counter + fetch index, (SP) + + ; 2) fetch arr[random(0, counter + 1)] + load SP, seed + fetch random_index, (SP) + + ; 3) swap values + store index, (SP) + load SP, counter + store random_index, (SP) + add SP, 01 + } + + vars(s7 is configa, s8 is configb) + + load s5, 0; originele permutation resultaat leeft op scratchpad index 0 + call permutation_to_configuration + output configa, ConfigA + output configb, ConfigB + + call pg_to_permutation + + load s5, 20 ; p/g permutation resultaat leeft op scratchpad index 32 + call permutation_to_configuration + output configa, ConfigC + output configb, ConfigD + + ; Terminate the simulator + ;load s0, 00 + output s0, QuitPort diff --git a/examples/test_randompermutations.py b/examples/test_randompermutations.py new file mode 100644 index 0000000..0ed6a62 --- /dev/null +++ b/examples/test_randompermutations.py @@ -0,0 +1,40 @@ +from opbtest import OpbTestCase + +# +# The randompermutations.psm4 Assembly file creates a random permutation of 4 numbers (0, 1, 2, 3) +# Scratchpad: +# 00 : 00 03 02 01 00 [...] -> original numbers, scrambled in unique permutation +# 10 : 00 02 02 01 00 [...] -> bitstream masks for these numbers: 0, 2, 2, 1 +# 20 : 00 01 02 02 00 [...] -> scrambled in unique permutation +# 30 : [...] +# Out ports: +# 00: 00 06 0A 0C 02 [...] + +class TestRandomPermutations(OpbTestCase): + def setUp(self): + pass + #self.do_not_cleanup_files() + + def load_psm4_assertion(self): + # mock out the PRNG call + return self.load_file("randompermutations.psm4").replace("call random", "load seed, 3F").execute() + + def test_random_range_between_three_and_zero(self): + # proc random_range(s0 is max, s2 is shifter, s3 is counter) { + # we need to initialize these registers as "parameters". "return value" is in sA (seed) + assert_that = self.load_file("randompermutations.psm4")\ + .replace("call random", "load seed, AF")\ + .setregs({"s0": 3})\ + .testproc("random_range")\ + .execute() + + assert_that.reg("sA").contains(2) + + def test_output_configuration_correct_in_output_ports(self): + assert_that = self.load_psm4_assertion() + assert_that.ports([1, 2, 3, 4, 5]).contains(["06", "0A", "0C", "02", "00"]) + + def test_permutation_in_scratchpad(self): + assert_that = self.load_psm4_assertion() + assert_that.scratchpads(["0", "1", "2", "3"]).contains([0, 3, 2, 1]) + assert_that.scratchpads(["10", "11", "12", "13"]).contains([0, 2, 2, 1]) diff --git a/opbtest/opbtestcase.py b/opbtest/opbtestcase.py index 817fd67..67d13ac 100644 --- a/opbtest/opbtestcase.py +++ b/opbtest/opbtestcase.py @@ -85,11 +85,18 @@ class OpbTestCase(TestCase): def assertPsm(self, jsondata): return OpbTestAssertions(jsondata, self) + def _print_expectation(self, expected): + return str(expected) + " (hex: " + hex(expected).replace("0x", "").upper().zfill(2) + ")" + def _check(self, jsonindex, jsondata, index, expected): actual = jsondata[jsonindex][index] + # try to convert both to the same type. acutal will always be an int (hex valued) + if type(expected) is str: + expected = int(expected, 16) + if expected == actual: return "" - return "output " + jsonindex + " " + str(index) + " should contain " + str(expected) + " but instead contains " + str(actual) + return "output " + jsonindex + " " + str(index) + " should contain " + self._print_expectation(expected) + " but instead contains " + self._print_expectation(actual) def checkPort(self, jsondata, port, expected): self._check("ports_out", jsondata, port, expected) @@ -100,4 +107,4 @@ class OpbTestCase(TestCase): expected = int(str(expected), 16) if expected == actual: return "" - return "reg " + bank + "," + str(nr) + " should contain " + str(expected) + " but instead contains " + str(actual) + return "reg " + bank + "," + self._print_expectation(nr) + " should contain " + str(expected) + " but instead contains " + str(actual) diff --git a/opbtest/opbtestmockable.py b/opbtest/opbtestmockable.py index 1fffac4..5a29419 100644 --- a/opbtest/opbtestmockable.py +++ b/opbtest/opbtestmockable.py @@ -34,7 +34,7 @@ class OpbTestMockable(): with open(self.filename, 'r') as original: data = original.readlines() - def findlinebetween(data, statement1, statement2): + def find_line_between(data, statement1, statement2): linenr = 0 startcounting = False for line in data: @@ -47,11 +47,33 @@ class OpbTestMockable(): if linenr + 1 == len(data): self.case.fail("No statements between " + statement1 + " and " + statement2 + " found") return linenr + def find_last_closing_bracket(data, procname): + procname += "(" # add (, could also have been a call + + linenr = 0 + startcounting = False + amount_of_brackets_open = 0 + for line in data: + if procname in line: + startcounting = True + + if startcounting: + if '}' in line: + amount_of_brackets_open -= 1 + elif '{' in line: + amount_of_brackets_open += 1 + if amount_of_brackets_open == 0: + break + linenr += 1 + + if linenr + 1 == len(data): + self.case.fail("Proc " + procname + ": no ending bracket } found") + return linenr def setupproc(data): self.prepender.append("jump " + self.proctotest + "\n") - linenr = findlinebetween(data, self.proctotest + "(", "}") # add (, could also have been a call + linenr = find_last_closing_bracket(data, self.proctotest) data = data[0:linenr] + ["jump opbtestquitfn\n"] + data[linenr:] return data @@ -89,7 +111,7 @@ class OpbTestMockable(): if len(self.replacers) > 0: data = setupreplaces(data) - firstjump = findlinebetween(data, "jump", "jump") + firstjump = find_line_between(data, "jump", "jump") data = data[0:firstjump] + self.prepender + data[firstjump:] + self.appender with open(self.filename, 'w') as modified: