Shameless plug: I had a similar impression when looking at nMigen, but wasn't super happy with Verilog either, so I wrote a new HDL called Wyre [0]. No metaprogramming, but a Verilog-like language with a focus on ergonomics instead. I'm currently making a basic Minecraft clone for the Lattice iCE40 with it.
HN user
nickmqb
I'm the author of Muon, a modern low-level programming language: https://github.com/nickmqb/muon
It did cross my mind. However, a problem with that approach is that evaluating such a formula is too costly/inaccurate on a small FPGA like this, which just has 8 DSPs (that can only do 16x16 bit multiplication), and some of these are already in use in other parts of the design.
Most textures look fine despite the 4-bit quantization, but color artifacts/hue change do become more apparent when multiplying with a light factor (to darken the textures on the bottom and sides of blocks), so I'd say 4-bit is definitely pushing the limit. 6 bits or even 8 bits per color component would be ideal, though unfortunately on a small FPGA like this we cannot afford such luxuries ;). It is probably the first thing I'd change if I were to port this to a larger FPGA, since it would be pretty straightforward to do and the increase in visual quality would likely be worth it.
VGA is an analog protocol, but the FPGA can only output a 0 (GND) or 1 (3.3v) on its I/O pins. I'm using a Digilent VGA Pmod [1] which uses a set of resistor ladders to map each color component from a 4-bit value to an analog voltage that goes to the monitor. This means that we have 14 pins: R (4x), G (4x), B (4x), HS (1x) and VS (1x).
[1] https://store.digilentinc.com/pmod-vga-video-graphics-array/
That's a good observation! This technique is also known as "racing the beam". The problem is a mismatch of refresh rates; the VGA display operates at 60 Hz but the ray tracer is not capable of producing that many pixels, it can only do 30 fps. So we need a frame buffer to store the result.
Yes, I am. This also means that any open source distribution won't include those textures. However, if I do end up open sourcing the work I'll make sure to include instructions for people that already own Minecraft; the textures are just .png files that can be extracted from the game's .jar file and can then be transformed to be used on the FPGA.
Yes and no. The design includes a custom built 16-bit CPU, which uses a custom instruction set, which I wrote an assembler for. There is a small 4kb bank of RAM that contains a program written in this instruction set. From a hardware perspective it is just data, but from a a software perspective it's that program that is ultimately responsible for running the game (by reading input from the gamepad module, setting up GPU registers, etc.).
That's a good idea. The two analog sticks on the GC controller would be an improvement over the single stick on the N64 controller for movement in 3D. I think that the main benefit of the N64 controller (besides a nostalgia factor, though that may just be me ;)) is how easy it is to connect. I actually just got some wire from the local hardware store, plugged pieces of it into the controller connector, and then attached some IC clips. For the GC controller, things are a bit trickier due to its connector layout, though I just found [1] which might be a nice solution; alternatively, buying a GC controller extension cord and wire stripping could be an option. I'll consider it!
[1] https://www.raphnet-tech.com/products/gc_controller_connecto...
Just do it ;). I don't have a blog post on how I got started, but you gave me the idea that perhaps I should write one.
In terms of hardware, I'm using an iCEBreaker dev board [1] which has worked really well (A nice bonus is that the board has a 16MB flash chip, which can be used for storage -- this is what the Minecraft clone uses to load the map and textures from on startup).
On the software side, I'm using the open source yosys/nextpnr/icestorm toolchain which is a lot faster than the vendor supplied tools. I mostly figured things out by just trying stuff, so a high iteration speed definitely helped here!
In terms of I/O pins that should actually be fine (current pinout: VGA (14x), SPI (4x), CLK (1x), N64_controller (1x)), though I'm still working on the project and I don't think it'll be done by the deadline. It looks like they might do future batches though -- it's a cool idea!
There are definitely FPGAs that are a lot more capable than the FPGA that I'm using. For example, see my reply to flatiron here: https://news.ycombinator.com/item?id=25172781
While it would have been easier to use a larger/faster FPGA, part of the fun of such a low level project is to work within harsh constraints and see what can be done regardless :).
I probably won't port it to other platforms, however, as I wrote in another comment, I am considering open sourcing it when it's done, so others can port it if they like.
I just looked up the MiSTer board [1]. They are using a DE10-Nano FPGA [2], which is a lot more powerful than the iCE40 UP5K [3] that I'm using (for comparison, the DE10-Nano has 110,000 lookup elements, whereas the iCE40 UP5K just has 5,280, so ~20x difference!). So porting should be pretty easy. It also opens up opportunities to increase the resolution, frame rate and render distance.
[1] https://github.com/MiSTer-devel/Main_MiSTer/wiki [2] https://www.digikey.com/en/product-highlight/t/terasic-tech/... [3] https://www.latticesemi.com/en/Products/FPGAandCPLD/iCE40Ult...
Yes, I'm using Wyre for this. Feedback is always welcome, so once you've had a chance to try it, don't hesitate to let me know what you think!
Thanks! That's correct, I built the ray tracing "GPU" from scratch. It's highly optimized because it needs to trace 256 * 128 * 30 = 0.98 million rays per second on very under powered hardware. It's specifically tailored to fast traversal of voxel grids. There's too many details to go into here, but as I wrote in another comment, I'm considering writing a few blog posts to explain how everything works in more detail!
Yup, that's correct, the design is implemented in Wyre, which is a hardware definition language that I created. The language compiles to Verilog so it can work with existing hardware development toolchains. The language is open source and can be found here: https://github.com/nickmqb/wyre
The FPGA that I'm using for this (the Lattice iCE40 UP5K) is really limited when it comes to RAM, which is the main constraint when it comes to world size. As per the title, there's only 143kb, which is insanely low for doing any kind of 3D stuff :). 48kb is used by the frame buffer, 19kb for textures, which leaves 76kb. 48kb of that is used for the map, which currently limits it to 32x32x32 blocks. However, I do have some plans to improve on that in the future!
The FPS (30Hz) is rock steady though! One of my pet peeves when doing DirectX/OpenGL development is that it's really hard to completely avoid frame drops, e.g. if the OS decides to schedule some other thread, or because the GPU driver decided to do something else than render your app. With hardware development, you can side step all of those problems. As a result, the Minecraft clone is guaranteed to not drop frames :).
The screen is connected to the FPGA over VGA. The output resolution is 1024x768 @ 60Hz, but the 3D portion of the screen is rendered at 256x128 @ 30Hz. The design consists of a custom 16-bit CPU (running at 32.6Mhz) and a custom raytracing "GPU" that can handle up to 4 rays in parallel.
Input happens via a N64 controller! Those are actually fairly easy to work with at a low level.
The code is not public, though I'm considering open sourcing the project when it's done. Moreover, there's a lot of additional details that I could potentially go into, so I'm considering also writing a few blogs posts with more info if people are interested!
You're not giving the older generations of programmers enough credit here.
While it is true that strong typing is a requirement for the best performance (and this remains so), the productivity benefits of strong typing have been known for a long time.
I mean, just look at languages like C# and Java. These are well established, extremely popular languages, used mostly in business software. A domain where performance is rarely critical. Yet, these languages are very popular. Not in the least because they make it easier for programmers to understand and work with other people's code, and because they provide good tooling, both of which are hugely valuable in a business/enterprise context. Strong typing plays a major role in enabling these features.
Even when C# was still a brand new language, roughly 20 years ago, Visual Studio already provided features like "go to definition", "find references" and autocomplete out of the box. These were a major reason for people to adopt the language.
It's no surprise that people like Anders Hejlsberg, who created C#, later went on to create TypeScript. They already understood the productivity advantages of strong typing and wanted to bring those to the web.
A list of widgets, with each widget having a x, y, width and height.
Do UIs need to be trees?
How do things change if we were to use a list instead? What becomes easier/harder?
Layout calculations might become slightly more expensive, though it seems that that may be acceptable since most time is spent rendering; and perhaps there are other ways to accelerate these calculations if needed.
The fact that most UIs are happy to provide a single linear tab order (and not something more involved that allows users to navigate the tree structure) makes me think that there might be something there.
I have a somewhat controversial opinion on this.
This paper, like some others that I've seen, treats parsing as an academic/algorithmic topic. Given some token stream we'd like to minimize some error criteria. However, these papers ignore the fact that people don't write token streams: they write code that is formatted using whitespace. I don't know any programmer that writes their source code on a single line. On the contrary, pretty much every programmer formats their source code to some reasonable (if not complete) degree. In other words: parsing error recovery is not primarly an algorithmic problem: it's a usability problem.
Newlines and indentation are a source of extra information that we can use to infer what the programmer meant. Why throw all that away during tokenization? That's crazy! We can totally use it for error recovery/error message generation.
I decided to play around with this idea in the design my programming language Muon [1]. The language uses redundant significant whitespace for parser error recovery. This simple approach by itself has turned out to work surprisingly well! In my admittedly biased opinion, it frequently surpasses the error recovery quality in mature compilers such as the C# compiler, which has had tons of effort poured into error recovery.
Of course, a purely whitespace based recovery scheme is not perfect: there are rough edges, like having to deal with tabs vs spaces, and recovering inside a line is not usually possible. But the fact that such a simple approach has led to good results makes me think this would be a great area for future research, that can perhaps combine the best of both worlds here.
This FPGA is not open source by design, but this project seems fairly complete: http://www.clifford.at/icestorm/ "Project IceStorm aims at documenting the bitstream format of Lattice iCE40 FPGAs and providing simple tools for analyzing and creating bitstream files."
Lattice (the FPGA's manufacturer) seems open-minded regarding such efforts: https://twitter.com/latticesemi/status/1269115302140231682
Does anyone know of any minimal TUI libraries? Something in the spirit of a VGA text buffer, that allows the user to manage their own cell buffer as a block of memory, and allows them to blit that to the terminal -- and ideally have that work across a variety of platforms and terminal emulators?
Avoid mixing spaces and tabs in the indentation of a text block
This has some implications. For example, I can imagine someone editing a text block and inadvertently using a different indentation type (tabs/spaces) from the other existing lines, which would then change their program in a non-obvious way. I'm myself an author of a programming language with a non-standard approach to whitespace (called Muon), so this is a topic I've thought about quite a bit. Interesting that the Java team considers this an acceptable tradeoff. I'm looking forward to seeing how this plays out in practice as a data point.
I'm working on Muon, a modern low-level programming language: https://github.com/nickmqb/muon
There is huge potential for a modern light weight C replacement. Golang went in this direction, but it has a runtime with a GC. Rust is low level, but is quite a complex language. So, there is a gap in the market.
One cool aspect of all of these new programming languages entering the arena is that most of them only have a minimal (or no) runtime. That should make it much easier to interoperate between all of these languages, something which has traditionally been difficult with most programming languages.
Disclosure: I'm the author of Muon [1], a low-level language that embodies similar design principles.
Agreed. Non-null pointers also come with their own set of problems:
- Increased language complexity. Initialization of (arrays of) structs with non-null pointer members is more complicated because there is no straightforward "default" value that can be used.
- Performance tradeoffs. Initialization of arrays and other containers is more costly for non-null pointers (or structs containing them) because we cannot simply zero out a block of memory.
- In memory unsafe languages it is possible for a non-null pointer to become null if its memory is overwritten (e.g. in custom allocator scenarios (also mentioned by the author of the article), interop scenarios, etc.). The type system now makes a false guarantee, and it becomes possible for a "non-null" pointer to trigger a null pointer crash. (It's worth mentioning that enums generally have a similar problem).
- I'd like do a more rigorous evaluation on this, but my gut feeling is that most null pointer bugs that I've encountered usually happen in situations where the pointer would have to be declared as nullable anyway, because I'm using null to represent a possible state.
I agree that Muon's current handling of whitespace is not ideal. I have plans to allow users to customize it. For now, you can use the //tab_size=N directive as documented here: https://github.com/nickmqb/muon/blob/master/docs/muon_by_exa...
One design decision that comes to mind is Muon's significant whitespace. Having that makes it much easier to do parser error recovery, which in turn made it easier to build the language server. Another one is that I initially had the rule that all typenames had to start with a capital letter, and nothing else could. Which simplified parsing even further, but turned out to be too restrictive, so I got rid of that. Your question is something I've wondered about too. How can the design of a language co-evolve with its tools? If you have pointers to existing research on this I would be interested to learn about it.
My main focus has been on the compiler and language server so far. Next up is a tool for generating foreign function definitions. This tool will make it easier to work with 3rd party libraries. Once ready, I'm planning on writing some examples, such as perhaps a small OpenGL program to demonstrate this functionality. Hopefully that will encourage users to give Muon a try for their own projects!