Okay, so I'll be presenting the F8 architecture.
For those who have been to last year's talk, the first year slides with the motivation
introduction will be familiar and the part will get into some more details.
Now the F8 is an A16-bedar architecture.
Now, why do we still, but it's A16-bedar architecture?
Well, when my point is still used in huge numbers, in a large number of devices.
Even the laptops, we must have what's occurring, we say,
tend to have an 85-divine, which is an 8-bit core,
as a keyboard controller, or somewhere deeply within the Wi-Fi chip.
For example, the real-tech Wi-Fi chip, you contain 85-divine cores.
Now, in general, there's even four-bit microcontrollers in U-SAM-TARM.
What the cycle computer, for my bike took it apart, turns out there's a four-bit microcontroller in it.
And of course, there's a familiar 32-bit 64-bit channel dominated by arm and probably some risk files.
But there's still this area of A16-bed.
Whereas a 32-bit devices are too big, not just in terms of their core,
but also in terms for memory requirements, both for program and data storage.
Now, is this low-end A16-bit architecture typically programmed in C?
They're not that expensive, the typical range is about a century euro.
So, some of them are a bit cheaper or more expensive.
The data memory tends to be in the range of about 60-70 bytes to about the same number of kilobytes,
and the program memory tends to be a few kilobytes, typically Samsung, like four or two hundred and twenty-eight kilobytes of a program memory.
The market is dominated by, for example, retiree architectures, like the STM8, by STMicroelectronics,
and then these ancient things like 80-50-wannes, or some of the 80-derivatives,
and such, were all patterns, have long inspired.
We don't have a good modern free architectures.
In a sort of two-six-four-bit market, we have a free drive, which is doing a very well,
but we don't really have this free architectures that has enough momentum in the 80-10-bit area,
and that's what I'm trying to change, better F8.
Now, a little bit of background, the small device, C-compiler, is a C-compiler targeting eight-bit systems.
In particular, architectures that are hard to target and clang, and DCC,
do two very irregularities.
It's a compiler that, other tools you need, like a sampler, linker, simulator,
round, it works on a variety of host systems.
It targets quite a lot of different eight-bit architectures.
It has optimizations that make sense for these eight-bit architectures, such as a register,
locator, that can deal with irregularities, get slow for a large number of registers,
which would be a problem if you use this in GCC or clang, but it's fine for eight-bit architectures,
with a small number of registers. The user group is embedded systems microcontroller programmers
and if you're computing enthusiast, because of all the eight-bit architectures,
as a part two, not just the current ones. Now, I'm a developer of the small device C-compiler,
and what I learned about the eight-bit architectures we are targeting,
what makes sense, what makes them a good target for C-compiler,
a lot of that experience, of course, went into the design of the F8.
And a really important point is, you want an efficient stack point, a relative, a tracing mode.
You put your local variable, you want to put some on the stack, because that's how you do
re-entran functions in C. If you don't have it, is that you have to give up re-entrancy.
By default, that's what you typically do when targeting the 80, 50, one.
Only makes those functions re-entrant, where you can use a real SS-K-I.
A nitri-cursion or indirectly or directly, or a nitri-entrant,
because I want to call it for mentor-up controller,
but if you have a efficient stack point, a relative, a tracing mode, then a tracing becomes much easier,
and you can do the right thing, and it's a efficient thing to do.
Then you want to unify that press-based, you want to say, okay,
this pointer, and you have your instruction for pointer access, and that's it.
You don't want to say, okay, this pointer. Now, let's look at the topic.
It's now we do a switch statement, there's five different cases, depending on which
address-based it is, I need to use a different instruction. Like you do have, let's say, the 85,
51, or quite some other of the less elegant data architectures.
Next point is, it helps to have registers.
Next, the experiment shows that there's the M8, for example, with its stack point relative
pressing mode, and unifying address-based, it's a bit lacking in registers, so there's a few
corner cases where even an old side of it can be more efficient. So you don't need a lot of registers,
but having some really helps this code generation.
Then the experience from the product devices is that, yes, you can replace peripheral hardware
by having an extra core or an extra hardware thread where you emulating your peripheral device,
and then do that in real time, because it's extra core, so that was at the rest of the system.
But you need to think it fully school, and you want it's supported at the sea level,
as a product devices, it's typically programmed in a sampler, and the notice in the instructions
that, for example, see 11 atomic, do not map nicely to that instruction set.
So you want support for these multi-stating anatomics and thread-local storage,
in your instruction set, so you can both programs or things in sea, and be able to
not use a lot of your die space for peripherals.
Yeah.
The point about your architecture is, we have a register-locator that can deal with it well,
and then they are fine for code generation. Now, DCC, whether it's or clang,
there's a typically track-installed graph, coloring a register-locator,
can't deal with irregular architectors well. You know, they want
kind of risk architecture, every instruction should have its register-operants,
and it shouldn't matter which register it is. If you have an instruction,
should be available for all registers, and should have the same cost.
But if the number of registers is low, and we have small targets, it's fine to deal with irregularities.
You want a good mixture of 8 and 16-bit operations, because you want to do 8-bit,
where you don't need more than 8-bit, because you have little memory,
but an 8-bit address space would be totally interfacial.
You need to 16-bit address space, and therefore pointers are 16-bits,
and then ends are 16-bit, and then see a lot of stuff gets promoted to end.
Often, you can optimize it out, but not always, so you need to be able to deal with 16-bit stuff.
And yes, as I said, pointers should be 16-bits, and if you want to unify that address space,
so pointer is a 16-bit value in memory, and we can use it directly, and we don't have
an upper byte, that is a text or for which address space it points, and then makes it 24-bit,
or things like that.
When getting to a few more details,
we experience a 16-bit address setting, that many 8-bit architect has suffered, actually, not that useful.
So you can use this as kind of a sketchpad memory, but the savings are relatively small.
You save an address byte in your instruction, so it makes it a fraction a little bit shorter,
but the user has to decide what goes into that 0-page or scratchpad memory.
It's much better to just have the stack pointer relative at pressing mode, be efficient,
and then the local variable is also one-factical, excess efficiently.
Index pointer relative read instructions for most 8- and 60-bit offsets
are important, it's particularly for like using existing stacks of union, yeah.
We want to be able to, to hear the, the pointer to the stack, now gets this memory.
And prefix bytes, which some 8-bit architect has, can actually be relatively good way to allow additional
operands. Now we want our instructions to be very compact, we can't afford like a
classic risk format with op code, target operand left operand right operand because that results in
big instructions. We want short instructions, and if we want to use different operands, then
where we do the rare cases via prefix bytes, so only instructions are actually
do unusual stuff get longer. That means we don't have the risk principle of every instruction
in the same length, but we tend to have different instruction lengths.
In terms of multiplication, yes, hardware multipliers are expensive, but the cheapest ones, the
8-time same to 16 is actually already quite useful, because first you can build bigger multiplications
out of it, and second the very common case of error indexing is such a multiplication. Yeah,
you have an area of struct, the struct has an odd size, because we don't want to part it to
multiple of 64 bits or anything, because again we don't have enough memory, so five bytes
struct, we need to do multiplication by five on the error access, and that is substantially
sped up by this multiplication. And yeah, the other multiplicative operators such as division
and remainder are relatively rare, so it tends to be not worse to actually have some in hardware.
On the other hand, having a multiply in that instruction can substantially speed up
white multiplications, because you have the multiplier, which is already costly, and you want to
use it efficiently, because you are paying that hardware cost, and then ideally you want to
do a big multiplication, 64 bits, you want to use the multiplier as often as possible, and
not spend all your time shuffling data around to make it available to the multiplier and then
store it away, so having dedicated multiply in that instruction helps a lot there. And after all,
multiplications are not just for the small ones important for the error indexing, but also for
things like cryptography, classic things like RSA, do multiply big numbers, and if we go into the
post-ventum area, let's say, the numbers here are at the transform of the chi bar key exchange,
also that's a lot of multiplications. Now binary code decimal, it's the first one, things are just
the dinosaur from the past, and as a data form it actually is. However, we said the vision is
really used, but still people often want to print out numbers in decimal, and then being able
to convert a number to BCD and then to asky is like a cheaper alternative to dividing by 10.
So having a little bit of BCDs are part of the hardware is actually useful for this use case of
outputting numbers in decimal. Yes, and we want a good support for shift annotations,
cause again, these are common when you're travelling bits, close to the hardware, if you
some maybe need a floating-point, some we're not familiar to the software, or also in cryptography.
So, where do we get there? Big picture, a 16-bit architecture, this will be an irregular
architecture, cause on the other side we don't get the code density we want, that means that the
core gets somewhat bigger, because we have some more complex instructions and instruction decode,
but we are saving a lot on the code memory, because our code this is maximum compact. And then
because we've seen there's a very low ant devices as well, I also want to define an instruction
subset, where the core becomes even smaller, this is as 8L variant, where the core is the only
about half-to-side for the others, but for example, the instructions are does not have a
multi-plicational at all. Now, looking at the details, this is basically the register set,
I came up with, sure we need a power-gram counter, counter, we need a stack pointer,
the 16-bit, our architecture uses a flag register with flag bits, meaning we are not directly
have a don't have a direct compare and jumping structure, but rather we do a comparison for us,
which sets flags in the flag register, and then we jump depending on those, and then we have
our general purpose registers, and in this case there's basically three 16-bit general purpose
registers, which each consists of two 8-bit registers. Basically, this is built up from the
experience, from the 8-bit target supported, such as the SDM H, such as the set 80 and all the
set 80 derivatives, and so on, and now which might not be perfectly visible, that XL and Y are
actually involved, because those are kind of accumulator. This architecture often have so-called
accumulator registers, where many instructions implicitly operate on that one, or only support that one,
as source and destination operand, and then you have these prefix flags that are mentioned before,
if you want to use a different register instead. So typically in addition, will be something like
takes a value in the accumulator, add something else to it, store the value into the accumulator,
and by default for the 8-bit structure, we use XL as the accumulator and Y for the 16-bit instructions.
If there's any questions on this, I think I'll try to leave time, because it's,
it's really the number of registers and having them as this two half is really based on this experience,
from the SDM H, where we do have an 8-bit accumulator, for example, and two 16-bit registers,
pointer registers, but we can't really use anything else as 8-bit registers, whereas for example,
it's at 80, where we actually do have a similar style of register pairs. We're often a 16-bit register,
can we use S2, 8-bit registers, and that also is affected co-generation.
And since I've been talking about instructions, and if you're not familiar with this style of instructions,
I thought we'd just do a typical example here. There's different interaction classes in
the F8, as usual for this architecture, one of them would be 8-bit instructions that have two operands.
Again, the very first one, the ABC-XL, with another output, be a typical case, which means it's an addition
of the Cary, we take the value in XL, 8-second operand, and 8-second Cary, store the result into XL,
and also updates the flags, even though it's not mentioned on the slides, such as a Cary flag,
to be able to build wider additions, such as the C-R flag, to use this for equality,
comparations and so on. And then the second operand, as typical in the CISC architecture,
can just be registered, but it also could be an immediate value or memory, or value in memory,
either directly at rest, with 16-bit address, relative to the stack pointer, using the stack pointer,
relative for tracing mode, or an indexed tracing mode relative to another register.
And the second and third lines here show how variants we get if we use a prefix by.
Now the first one, prefix is a prefix by for swapping the two operands. So basically instead of
taking XL adding something, sorry it's there, while we use the location,
and add something else to it, and store the result, and then sorry, that's not a second, that's a
third line, this one here, I was talking about here. We basically swap the two operands,
we are instead of having XL as a source and destination, we have the other thing as a source and
destination, we add XL to it, that basically allows us to use one of the other registers,
or a memory location, like we use the actual molecular before. It has a prefix by it,
this interaction costs more, but it allows us to make better use of the other registers, and
allowing to even have a memory operand means we can use this as a atomic instruction.
Not that important for an 8-bit addition with Kerry, but in general, and this, if you want an atomic
addition, and they have it directly operate on the memory operand, that is how you can do it efficiently.
Okay, now after the second line, we just change the XL via a different one, so instead of swapping
the two operands and having XL as a right operand, we still have the right operand like before,
but instead of XL as a accumulator, we use a different register. So for the second line,
it's always a register. That's one of the source of destinations, only for the swap
the one where a source one can actually have memory as a destination.
It works similar with the 16-bit interaction with two operands,
or if you have an 8-bit interaction with just one operand, like a rotation or shift
the interaction, because of course it doesn't make sense to swap operands, but you still have
a prefix by where you replace the XL accumulator by one of the different registers, or even a memory operand.
Typically, you, in the compiler, then you keep commonly accessed values in registers,
but if you're running out of registers, for example, you make sense to have a decrement,
that works directly on the memory for your loop counter if your loop counter is not accessed much.
Inside the loop, otherwise. So I was only mentioned that there's other instructions,
so the rough instruction that overviews, we have this 8-bit tour operand instructions,
where we saw the example, we have the one operand instruction, like a decrement or shift,
similar instructions exist, 16-bit white, not as many, but still enough that we can work well with
integers and with pointers, and then of course there's the load instructions,
where we're moving data around between registers and memory, or even in some corner cases
between memory and memory, and then there's a special case once, the other 8-bit instructions,
other 16-bit instructions. Typically, you always have like a few instructions that you need for
extra stuff, that doesn't really fit into the other categories. Like a sign extension instructions,
especially in the 8-16-bit area, you often work in both 8-16-bit data, if you want to convert to
signed 8-16-bit detection instructions to start properly sign extensions, allowing a dedicated instruction
for that, it's nice, or something like this multiply and add instructions, which gets more complicated,
that would be an example of one of the other signs instructions, and so forth, the jumps,
unconditional jumps, conditional jumps, depending on the flag register,
instructions to call, sub-potins, to return from them, like put the program count on the start,
and so on. Yes, to make the hard-famplementation a bit easier, all instructions to
write at most 16-bit register and 16-bit memory locations, they can read multiple registers and
read one memory location. So the idea is that you can, in hardware, implement this,
where the dual-port RAM and the single-port program memory. And to be able to use memory efficiently
and not need any padding by it, there's no alignment requirements, meaning the architecture allows
16-bit loads and stores to any address, not just to even ones. To again, Max hardware would
more complicated, but it saves a lot in terms of memory design. And there's actually a few
safety and security features, which from perspective of a bigger microcontroller, of course,
nothing particular fancy, I mean, on a risk, on a arm you have a wide extra execucing sensor,
we don't have that. But I've put in a few few basic stuff, the classic watchstock, you want
on a microcontroller, meaning it's the system hangs, you have a mechanism to restart it,
basically it's the counter, that counts down or up, and when it reaches the set value, it just
resets a machine, and then you're software, you tell the watchstock to be quiet for a while,
all the time, and if you don't do it too long, the system assumes that it was hanging,
and then it just brings starts. The second one is actually wrong, what I would say,
wide to address, zero to trap, not reads. So if you can't, for some reason, an all-pointer,
and it's up somewhere where you're worried that you don't get random undefined behavior,
but you get a proper reset, a trap reset, in similar, if you're in Star, the zero instructions,
the opt code that's all zero bits is all so a trap. Classic, classicly, this is often a no-op,
and in many exploits, this is used, because when memory is often zero, things are often in
it's like to zero before they're used, and if you manage to exit, to start executing some data,
it's all no-ops until you get to a point where you have your gadgets that you can use to build
your exploit, but if there's a no-ops hero instruction, it's actually a trap instruction that
resets a system, this relatively common attack style doesn't work anymore. And of course, after
reset, we have some bits in the reset controller set, so after reset you can check what was the
reset, what was the power on reset, what was the watchdog reset, what was the trap reset.
So basically that's what I wanted to say about the details of the architecture so far,
but if I have extra time, I'll use it to quickly show the op code map,
but let's just get to the current state. So there's an F8 port, and also an F8 L port,
in SDCC's small device C compiler, so we have a working C tool chain for the architecture,
I'll put in very low implementations of the F8, two of the F8, one of the F8 L,
moist proof of concept, then not really a C-plue designer, even though I have a bit little bit of computer
architecture background, one bit more optimized for speed, the other one more for size,
the digital prepository, the architecture manual, and from tutorials on how to get started,
on some common FPGA boards, for example, the letters, I see E40 ones,
I think it's a govine, I would have had running on govine and on the
current chip skate mate, so the ones I tend to have free tool chains, didn't bother trying another,
just of course the website of the small device C compiler, and by the instructions, that is fixed by
now, I'm still traveling the op code map around a bit, mostly automated to see what the
synthesis tools can use very well to make the core a little bit smaller, so
we'll probably see in the next few months the op code map being fixed, and then the sampler,
and simulator has been updated accordingly. Okay, so that's for my talk slides, now if you want to
see the I hope bits, it's readable, even from the back a bit. Now, we have,
like, maybe I can, can, can zoom in a bit.
Ah, yeah, I see it. Thanks.
Better? Okay, so on the upper left, of course, we have the top instruction, as I said,
C.O. is a top, then we have the typical two-up on the eight bits, they are, we use the addition
with carry as example, where there you see the subtraction, subtraction with carry addition, addition
with carry, the comparison with which is essentially the same as subtraction, but it doesn't
write it's destination, it's just for checking things in an eighth condition or in a switch statement.
The usual bit was operations and or XOR, the next comes the one-up on the eight bit instructions,
such as the shifts, right shift left shift rotations, increment, decrement, clearance,
or setting to zero, and just testing setting the flag beds depending on the value.
Now, you see the first yellow ones, here the yellow ones are, if eight instructions are not in the
simplified F8, L sub set, and when we now get to some 16 bit instructions, you see that a lot
of them are F8 only, then we have a set of load instructions, next comes the exotic things,
what not really exotic, but to once that doesn't fit into another category, like the exchanging
tracks, an observer which swaps the two operands, atomically again, the grey ones, by the way, are these
instruction prefixes that are mentioned before, and then 16 bit loads, L, the W, anything,
ending in W is a 16 bit instruction essentially, and we also have a 16 bit XOR and or,
because it's a relatively common in cryptography and the OR also in bitchar flings actually,
both quite a bit more common than a need for 16 bit end instruction, because a lot of real
world uses of end operators in C, we do something like a Texas, with an end mask,
can serve with an end mask, can do stuff with it, then then OI together, but at the time we
OI together we really need an OI instruction, so at the time we did the end, one of them was still
a literal operand for typically one byte, what is a fully one or fully zero, so it's
got optimized in turn 8 bit aren't any way. When they're as a conditional jump, the sign
expansion stuff and multiply an art dancer on. Now looking down, this is basically the first page
of the op code map, and if you use one of the prefix bytes and you get into something like this,
this is the one for swapping operands, you can see now for subtraction the XL is on the right side,
and the other operand is one of the other types, and then instead of the swap operand prefix
we also then have of course this was the other pages for one of the other prefix is in this case
XL 8 bit accumulator gets replaced by the XH register for this prefix byte or down here and
out the YL and so on. So these other pages are just the prefix byte applied to an instruction,
which affects the operands, what else are we as the same, maybe the small exception of the
conditional jumps, where it affects the condition that we are jumping on.
Sorry, the conditional jumps are wanted to highlight the actually easier over there,
so this is a jump on carry flux set for example instruction, jump on zero flux set,
instruction and a few less common conditions are then moved to the next page.