Hi Felix
At the moment, my toolchain of choice is pen-and-paper.
I'd like to have a reasonably clear idea of the necessary micro-
architectural features before starting to write code. One thing I have
decided upon is that I would start with a fully microcoded design.
No doubt it will be SLOW, but that's something to improve upon later...
isn't Uxn meant to stay around for 100+ years? :)
This way it will be also easier to catch up if the ISA still evolves.
And it might turn out to be a design simple enough to be understandable
_in full_ by a hobbyist. I think there would be value in that.
Once it does come to writing the code, I might look at some of the
fancy modern HDLs, or just go with SystemVerilog. I would be targeting
the (awesome) Yosys toolchain and the open-hardware ULX3S board.
I'll report back when I have something tangible to show off ;)
Just one question... is there any kind of test suite for the Uxn ISA?
The tougher, the better -- or basically any body of CPU-heavy code that
I could throw at a prototype implementation to test it for compliance/
regressions, as that's always a big concern in logic designs.
Appreciate your interest!
Best,
Martin
> At the moment, my toolchain of choice is pen-and-paper.
That's the best toolchain, indeed. :-)
> Just one question... is there any kind of test suite for the Uxn ISA?> The tougher, the better -- or basically any body of CPU-heavy code that> I could throw at a prototype implementation to test it for compliance/> regressions, as that's always a big concern in logic designs.
There is projects/utils/test.tal in the uxn repository and I've found this
via awesome-uxn: https://github.com/DeltaF1/uxn-instruction-tests
That's all I personally know of, there might be more.
felix
Pen & Paper is definitely the way to go for this :) I'd love to follow
along, it's also things I know nothing about, but I'd like to learn
more about hardware.
This testing program only requires that you have the DEO and EQU
opcodes to get started:
https://git.sr.ht/~rabbits/uxn5/tree/main/item/etc/tests.tal
Hello again
I am getting closer to having a baseline implementation of bulk of
the CPU spec, meaning all instructions, stacks, RAM+device memories and
the 3 System traps. Thus far implemented as a hardware model in Python:
https://github.com/mcejp/lux
(you will see very quickly that this is not just another
run-of-the-mill emulator :)
There is a long road ahead both in terms of hardware feasibility and
execution speed (I haven't even added a direct datapath from Top-of-
Stack to ALU... not good!)
I have not started documenting things, either.
Synthesizable code is still far off, but if you're curious, you can
start playing around with the model today and probably find a good
number of bugs. Instructions are in the README. Right now the CPU can
run all of uxn-instruction-tests, and most of Devine's tests.tal
(choking only on the stack under/overflow)
I used Hylang to build my microcode, by the way. It's a very neat
LISP-in-Python, perfect for DSLs and worth checking out.
(I hope that this time my reply does not spawn yet another thread :|
Sorry.)
I'm thrilled to see this come together, I don't have the internet
bandwidth to clone anything at this moment, but I'm excited to follow
along the repo until we sail back to civilisation.
You're the first person to use the uxntal test rom, do you have any
suggestion that you'd like to see, I haven't really shared it yet, and
I'd love to improve it if possible.
There's also no uxntal implementation guide, if you have some
pointers, or places that tripped you, let me know. I'd like to take
this opportunity to better document the project as well.
Since you're not planning on covering devices, would something like
busy beaver help you test things out? Are you using LED lights or
something to validate if a program is working?
The test rom is going to be immensely helpful, I’m working on reimplementing uxn in rust, as a project.
> On Jun 25, 2022, at 8:54 PM, Hundred Rabbits <hundredrabbits@gmail.com> wrote:> > I'm thrilled to see this come together, I don't have the internet> bandwidth to clone anything at this moment, but I'm excited to follow> along the repo until we sail back to civilisation.> > You're the first person to use the uxntal test rom, do you have any> suggestion that you'd like to see, I haven't really shared it yet, and> I'd love to improve it if possible.> > There's also no uxntal implementation guide, if you have some> pointers, or places that tripped you, let me know. I'd like to take> this opportunity to better document the project as well.> > Since you're not planning on covering devices, would something like> busy beaver help you test things out? Are you using LED lights or> something to validate if a program is working?
In my view the main strength of the test ROM is that it is plain Uxn
code with minimum runtime requirements. What surprised me was that the
test begins by mapping the stacks to RAM. Isn't this a Varvara-ism,
i.e. outside of Uxn spec?
Also, if the under/overflow vector test fails, no error is printed.
Ultimately, though, I feel like the approach of a hand-written ROM can
never be sufficiently comprehensive to thoroughly test non-simple CPU
implementations. For example, there is no testing of k/r modes, and to
add it in you would basically need to increase the amount of code 4x.
If we really care about validation of implementations, it would be
incredibly useful to have some parse-able (!) formal specification
of how the machine state is transformed by each instruction.
For example (syntax arbitrary):
opcode ADD:
if pre.wst_pos < 2:
# stack underflow must error out
post.wst_pos == 0
post.wst_err == 1
post.pc == pre.system_vector
else:
# standard byte Add
post.wst_pos == pre.wst_pos - 1
post.wst[post.wst_pos - 1] == (pre.wst[pre.wst_pos-1]
+ pre.wst[pre.wst_pos-2]) & 0xff
From this, one can generate a heap of test vectors + assertions
focusing on the edge cases (near-stack-overflow etc). Though probably
not all important state is observable from inside the VM, so
interpreter cooperation would be necessary.
Some things that tripped me up in implementation:
- how different instructions are affected by S-mode
- how k-mode works
- byte vs short access to device space (turns out it works just like RAM)
- multiply and especially divide are much more expensive in hardware
than other ops. But for a generic VM it makes sense to have them in
the instruction set.
In the end I mostly looked at uxn_eval() as a source of truth.
As I'm not testing anything on hardware yet, I don't have LED outputs.
When necessary, I just generate micro-op traces on stdout.
I should probably have removed the System/wst deo from the tests, I
wrote this test rom for a varvara implementation, but I could remove
it, it hardly uses it.
I had the idea of making this test rom to get people started, after I
saw that the chip8 implementations often worked that way.
I don't know anything about assertion, verification, all that sort of
thing, so I probably won't be the person to write an implementation
guide.
Especially since i've been neck deep into this stuff for so long now,
that I have trouble looking at this from an outside perspective,
that's why I think you're at the perfect place, if you're up for it,
to improve the documentation.
Could you explain the k mode in your own words in that way that could
be useful to other implementers, so I could put it on the docs? Is
there a way we could make the uxn.c more explicit?
> byte vs short access to device space
What do you mean by that?
Thanks for the feedback :) I think this could be vastly improved.
On Thu, Jun 30, 2022 at 15:42:59 -0700, Hundred Rabbits wrote:
> Could you explain the k mode in your own words in that way that could> be useful to other implementers, so I could put it on the docs? Is> there a way we could make the uxn.c more explicit?
I'm not Martin, but my own thoughts: (pretty much a translation of uxn.c):
Normally, operations get values to work on by POPping sequentially, but the
k mode causes POP to do nothing to the stack, or it POPs from a copy of the stack.
So any operation which needs to read some value, even if it will leave it there,
would usually
a = POP(); PUSH(a);
...other operations using a
but in the k mode, it's more like
a = PEEK(); PUSH(a);
...
This requires a slightly different way of thinking about some opcodes:
for example, DUP: instead of saying it pushes the value on the top of
the stack, say that it reads (pops) the top value, pushes it back on, and
pushes the same value again.
Hence #00 DUPk produces 00 00 00.
Hm, that didn't turn out to be clearly worded or concise, or more
understandable than what's currently in your docs. Ah well. I think the
best way of understanding the modes is by inspecting the output of each
opcode in each mode, as in the examples in the uxntal reference.
However, that's not a formal specification.
phoebos
Regarding k-mode,
my model of it is that each stack has in fact separate read (pop) and
write (push) pointers. Normally these are tied together, except in
k-mode where they move independently. However, they are still connected
with a bungee cord which contracts at the end of every instruction,
teleporting the read pointer to the write pointer.
This model works since you never have POP-after-PUSH from the same
stack within any single instruction. In fact, this is how I implement
it in hardware (minus the physical bungee rope)