Loving the content Fabian, both the hardware & software side.! The way you manage to explain what you are doing is great. Can't wait for the next installment. 👍
Thanks! 🙂 I do love compilers and the related technology, and sometimes the best way to learn things is to build your own simple version from scratch 😃
Really enjoying your content Fabian. I recently implemented a 6502 emulator in Swift and have been pondering creating a 6502 Assembler in Swift. I think this video just gave me a template to do so 👍
Really love the way you implement the assembler! Have looking forward to this video since I started your video series. Now I want to write my own assembler starting from the work you have done ^^
The "JABSR" instruction should really just require the "low register" as you enforce a low-hi register anyway. So valid inputs should be R0 through R6, with the assembler adding the hi register. It cuts out an unnecessary extra parameter that only adds an opportunity for error and nothing else. Unless you plan to change this late or if you wanted to show some more complex parsing.
Yeah that would definitely be a good option! And it would get rid of a source of error where the programmer has to uphold a contract that isn't really necessary. 👍 My reasoning for keeping it was readability, to make it obvious when reading the code that two neighboring registers are used.
@@fabianschuiki Since this is a hobby project there is no need to make it too user friendly. And you are absolutely right about the readability aspect. As implemented it is very obvious that JABSR requires two registers to function and that avoids any nasty surprises to anyone not familiar with the instruction set.
I'm way late with this so you probably noticed in a later video but IIRC the parser doesn't take into account that the register pairing isn't implemented for the last register in hardware.
I think you might be right. I don't recall entirely if the encoding step ensures that the lower register can't be the last one in the stack 🤔. I'll need to go check that. Thanks for the pointer!
Brilliant. Will you be adding support for labels? Then your jump instructions can reference labels. For this you might need to parse the source code twice. Once to collect all the addresses of your labels and again to parse the instructions and substitute addresses for labels in the operands.
Thank you! Yes I'm definitely planning to add labels and expressions in general. Being able to jump to labels instead of fixed addresses is going to be extremely valuable :-). And since the assembler parses the instructions into a data structure (the program array of instruction objects), there's no need for double parsing. We can simply do a first pass over the parsed instructions to determine their addresses and layout, and then a second pass to resolve all labels to concrete values!
@@fabianschuiki that will be faster than the way I thought too do it. Nice. Next up is macros... I'v been enjoying watching the progress of this project.
Macros are a cool idea! 😎 Once you have the instructions be objects in an array, there's nothing stopping you from replacing macros in the array with their definition 🙂
Great start. I think I'm going to adapt some of these ideas for an command line assembler I want to implement for my emulator. I once wrote one without the benefit of regex and classes/structs and it was pretty disgusting.
You skipped one test while testing the number parser: what happens if you enter "-0b1001" as a intermediate value? Is this supposed to work? Or shouldn't the negative sign only be viable when using decimal numbers?
Good point! This will work. The rationale being that just changing the base of how you would like to note down your numbers shouldn't also restrict you to positive-only values. Probably not a lot of practical use for this, but still, why limit the possibilities 😏
Recently found this channel and have now caught up. Always interesting to see these processors using discrete logic grow. It will be especially interesting to see how it will move towards superscaler and did I hear mention of OoO? Have a few questions around the design so far: I hope I’m not duplicating questions from other instalments. - I’ve noticed that the register with register address zero (as opposed to r0 which has address 1) is unused. I assume this is being saved for some future use, but I also wondered if a simple always-zero register could be constructed, maybe with some pull downs or with a register board where the register chip is omitted and the buffer chips' inputs hard-wired to zero. - I’ve seen at least one old discrete-logic CPU (for the PERQ workstation) where registers are implemented by SRAMs - with each read-port being achieved by duplicating the SRAM. I'm guessing such an approach would be viable here, although maybe at the expense of having to consider when registers are written to vs when they’re written. Other trade-offs, I suppose, are whilst you’d get a single small register file board, you’d lose being able to see all register contents visually with LEDs. - I did also ponder if register boards like the current ones could be designed to stand vertically, with their visualisation LEDs along the top edge. I guess the only real benefit here is the space required for registers on the backplane; don’t know if seeing the LEDs closer together helps visualise the content of the overall register file better or worse. Anyway, these are random musings of a new viewer, I rather like what I’ve seen so far and am just contemplating alternatives rather than suggesting change 😊
Out-of-order is definitely on the menu. From a hardware/breadboard point of view it'll be easier to implement than sustained superscalar execution, which requires the ability to fetch and issue multiple instructions per cycle (lots of additional hardware). - I like your idea of using register address 0 as an actual zero register! RISC-V and OpenRISC did that as well. That would be a neat addition. Just have a buffer that drives a constant 0 when enabled, as you say. - SRAM registers are definitely not a bad idea. GPUs also work along these lines. The problem with SRAMs is that it's very difficult to have multiple write ports; multiple read ports definitely work with your suggestion, but for multiple write ports on the register file you'd actually need a multi-port SRAM, which are hard to come by. Later on when we get to out-of-order execution and reservation stations, we'll want to have additional comparators in the registers such that they can check themselves when the result they are waiting for is broadcast -- that will be impossible to do with an SRAM. - Yeah upright registers would be cool. I was generally thinking that doing a second iteration of the CPU with most boards upright and compact could be very interesting, and it would help reduce the infinite number of pin headers needed 😉 Thanks for the great suggestions!
@@fabianschuiki Regarding the upright register boards: would be nice if they are at an angle like the old SIMM RAM. That way the LEDs would be nice readable and they would take up less space.
50:09 Do you plan on combining multiple operations into one assembly instruction? For example you could write a "jabsi 0xF040" assembly instruction which would result internally in mv r0, 0x40 mv r1, 0xF0 jabsr r0r1 with some registers specific for such operations all 16-bit operations could be written as one line in assembly. Also I would love to see the jabsr, jreli, jrelr instructions to be combined into one jmp instruction and the assembler decides what jump instruction is used. (feels more like the real world assembly languages I am used to ^^)
Yeah that's a great idea! Assemblers tend to do some of housekeeping like this for you. Most RISC-V toolchains have a few pseudo-instructions like `li` (load immediate), `la` (load address), and `call` that expand to multiple instructions that do the right thing. One useful example for my assembler would be `la r0r1, some_label`, which could expand to `ldi r0, lo(some_label); ldi r1, hi(some_label)`. Jumps and calls would also be nice to represent like this, with the assembler inserting the appropriate instruction (`jreli` for short distances, `jabsr` for long distances). Very cool idea!
It's not a "hashtag" character, it's a hash or poundsign. A word that begins with that character is a hashtag, ie. when someone says "hastag foo" this doesn't mean "hashtag character, foo", it means "this is a hashtag: foo".
47:40 I was just wondering why you need to specify a register pair. Why not just define the jabsr operation as "jabsr [rs]" and define that the hi byte is always in rs+1. Is there a special idea behind the use of a register pair that maybe is more than one register index apart? Like r0r3?
The idea was to have an explicit alias for the register pairs, and to separate them from regular registers. That way the assembly text clearly shows when an operation takes two registers instead of just one. Especially later on, if we get around to extending the assembler to also do things like register allocation, it will be important to accurately capture what registers an operation reads. I actually got the `r0r1` idea from x86 register names: I was thinking about having registers a, b, c, etc., and then allowing for pairs like ab, bc, cd, etc.
That is very strange. Especially if your code is the same 🤔. Any idea what might be going on? Printing out other values along the way can often help pinpoint a problem.
Even though I understand your explanation of what the code is doing, for some reason my brain still cannot parse how the code actually represents what your explaining. Still watched and enjoyed the video though. 😊
@@fabianschuiki your explanation of what the code is doing seems to be pretty clear, and I'm guessing for someone who has even the most basic understanding of writing code would be able to follow along quite easily. Any more and I think it risks becoming more of a programming tutorial rather than a hardware design. The issue is firmly with me. I've tried to learn to code, but I tend to lose interest and go back to playing with hardware instead!
@@fabianschuiki your explanations are excellent and i appreciate very much that you don't assume that everybody has profound programming skills. I have to watch and listen carefully what you do. Maybe give the viewer a little more time to pause the video at certain steps, so that i can type.
@@BerndSchymanski you can always pause the video if you need more time, that's what I do, along with zooming in on the freeze frame to see more clearly.
Thanks! 🙂 I'm always torn between adding more pauses to the video itself, which may bore people that just want to follow along at a high level, and letting people pause the player, which may be cumbersome 🤔
Haha, it's horrible I know! 😏 But this way there are no surprises regarding comments. Not ideal for a programming language, but for an assembler maybe not too bad.
I feel you 😁! I definitely would prefer a type safe and properly compiled language to write these tools. Probably Rust, C, or C++. But there's something to be said about Python's absolute and utter simplicity when it comes to just get something going. Especially for trying to explain what's going it's great if the language steps out of the way completely. We might still do a port to Rust at some point though 😏
Learning Rust the Fabian Way? 😊 Back in the mists of time, when I was at college, I wrote a 6502 assembler on my Commodore Plus/4 in PET BASIC… now that’s an exercise I’d suggest nobody tries repeat 😂
@Fabian Im a software dev, C# mainly. To me just having a type declaration there makes things much clearer. Without it, i feel very unsafe because i cannot bring myself to trust in variable names. @Timothy In my free time, I am a solo game dev. I like simulation games; especially very, V-E-R-Y detailed ones, resulting in millions of entity instaces per type. You need all types of parallelism for that (Cores, Data, Instructions), with SIMD being one of them. For non trivial SIMD code (aka summing up integers in an array or something lol), you pretty much need assembly/intrinsics code. I wrote a like 400k line math library mostly in intrinsics (but also with actual x86). It's still relevant today and in real time applications very commonplace.