Skip to content
HN On Hacker News ↗

More like SC-Forth-thousand, am I right?

▲ 20 points • 1 comments • by stevekemp • 1w ago • HN discussion ↗

Pangram verdict · v3.3

We believe that this entire text is human-written.

0 %

AI likelihood · overall

Human
100% human-written 0% AI-generated
SEGMENTS · HUMAN 1 of 1
SEGMENTS · AI 0 of 1
WORD COUNT 1,694
PEAK AI % 0% · §1
Analyzed
Oct 3
backend: pangram/v3.3
Segments scanned
1 windows
avg 1694 words each
Distribution
100 / 0%
human / AI fraction
Verdict
Human
Pangram v3.3

Article text · 1,694 words · 1 segments analyzed

Human AI-generated
§1 Human · 0%

The Soggy needed a good low-level programming environment with which to tinker on future hardware projects, and ideally it was one that I controlled myself, so I could include it on the onboard ROM without infringing copyright. It would be nice if it used a semi-popular programming language for embedded systems, and had an interactive development environment that I could run right on the machine. All that seems to mean I’m finally going to make my first Forth. The Soggy? Sega’s first home console and their first home computer are very similar machines. Although the exact reasoning hasn’t been said (to my knowledge,) Sega originally planned to release multiple home computers, at different levels of capability. This plan changed to releasing just one home computer (the SC-3000) and one game console (the SG-1000.) Unfortunately for them, the SG-1000 released on the exact same day as the Nintendo Famicom, but Sega considered it to be a success anyway. What’s interesting about the shared heritage between these two machines is that you can attach a keyboard to an SG-1000, in order to turn it into sort of a de facto SC-30001. A lot of companies promised stuff like this in the 80s, and Sega delivered. As a result, you can run (a very limited form of) BASIC on your SG-1000. Although I have a very fragile and deteriorating SC-3000, I wanted something I could play with without worrying about shattering delicate keyboard plastic on. The prices of somewhat-more-durable SG-1000s are high and on the rise, especially the very Shōwa-futurist first-generation model. I also had experience with making a clone of the ColecoVision, which uses many similar parts to the SG-1000 (but is not cross-compatible in any way.) I ended up making a clone of the SG-1000, called the Soggy-1000, and made sure to make it compatible with the SG-1000 keyboard. Naturally, with this kind of ambition and/or excess free time, I quickly ran into the limitations of Sega’s BASIC. A real computer should be able to build programs on and for itself! RetroChallenge This project is done as part of the RetroChallenge, a quasi-annual informal competition where everyone gets together and does something cool with retrocomputers for an entire month. This is for RetroChallenge 2026/10. I find that the event helps force me to work on one project, instead of bouncing around between a billion ideas, and so I can get big leaps of progress out of my projects in just one month. By the rules of RetroChallenge, you are not supposed to start your work early, so that’s why this article is coming out at the very start of the month. It’s all stuff that I have done months, and in some cases, years prior, and the real “challenge” is just getting me to finish it. Everything from now on is part of RetroChallenge, and this article should serve as a primer to figure out just what it is I am trying to do. Okay, on with the Forth. Why Forth? In case you’ve never heard of it, Forth is a very minimal high-level programming language. It’s very common in embedded and small systems, mostly because it’s easy to get the base system working and then lets you incrementally build some functionality using a command shell. Think of the Python REPL, and you’re on the right path. It takes awhile to wrap your head around the stack-oriented design, but once you do, it is surprising just how elegant the language is. What the Hell is Forth is a great read on the subject, which I saw kicking around the internet only after I started on this ridiculous project. Another big appeal of Forth to me on these limited machines is that, in many distributions, Forth often includes an integrated assembler. With a suitably equipped Forth interpreter2, you can bang out assembly language programs in a REPL on the real hardware, which is an experience you can’t really get anywhere else. The biggest grain of salt to take with this article, and pretty much every future one I write on the subject, is that I don’t really know how to write programs in Forth. I can hack together a simple program with some serious effort, but I still need to jump back to the documentation to understand a lot of the jargon, and I am almost useless at reading code. Do not consider me as a Forth expert, or even an enthusiastic amateur. The point of this series is to show how easy it is to bring up a Forth interpreter on any random computer you may encounter. If you’re an experienced Forth programmer, please forgive me any screw-ups in advance. Why Forth on the SG-1000/SC-3000? With Forth on the SC-3000, I can write all kinds of programs, in a more space-efficient way than with BASIC3. It’s also, frankly, less irritating: I got more than my fill of renumbering large BASIC programs in the 90s. Forth’s higher-level and more structured than assembly, which means I can write programs much faster without stepping on myself trying to remember the semantics of otir. If I want to, I can even include an assembler, so I can write really fast, useful code right on the machine without having to involve a “real computer” to cross-assemble for Z80. Last, with the ROM-socket on the Soggy, it also provides a useful platform to make it more useful as a general-purpose computer, with a powerful programming language that’s ready to use from a cold start. I can even write device drivers for new stuff connected to the expansion slot! Like the aforementioned article says, it’s like an assembly REPL. While I was working on this project, I also found an amazing article in Kilobaud magazine, called, well, Write Your Own FORTH Interpreter. If you’re interested in the mechanisms of how these things really work under the hood, make sure to open that up in a second tab. Or third, or 400th. We’re all friends here. Port a Forth I first wanted to start out by porting a Forth. This would cause me to get a lot of the “SG-1000 specific” stuff out of the way, plus maybe give me some good ideas about my implementation. The RomWBW project contains a fork of the GPL-licensed CamelForth-80, by Bradford J. Rodriguez. CamelForth is originally intended to run on CP/M-80, but the author has thankfully provided a thorough porting guide for how to modify it to run standalone, out of ROM. I have to write my own hardware initialization (TMS9918, stack pointer, etc,) a reset handler, move some buffers around, and replace three words that are implemented to use CP/M. Those three words, implemented in Z80 assembly, are: KEY: Returns the key being pressed; KEY?: Returns true if a key is waiting; EMIT: Prints a single character to the screen. It’s really nice to see a thorough porting guide like this. I read through the source code and tried to come up with a plan for attack, merging it into the codebase of my RAM testing program in order to provide a console interface and basic keyboard-reading functionality. Since the RomWBW version was modified, I ended up grabbing the original from the CamelForth website and working from that. Getting it to Assemble You might think all Z80 macro assemblers are pretty much the same. However, there’s a lot of differences in syntax, the order of operations, and especially in macro implementations. CamelForth-80 was originally built with the freeware/public-domain Z80MR assembler, but I have been using Zasm for all of my Z80 projects to date. I don’t have a particular attachment to zasm, but Z80MR is meant to run in CP/M, and as far as I could tell did not have a modern Mac/*nix port. In fact, if you do a web search, the majority of surviving references to its existence are from CamelForth-80’s own readme. I had to hunt around for a little while until I found this copy of it in the DiscMaster archive of the Oakland CP/M archive CD, where it was distributed in (what else?) Z80 assembly. So: I’d have to port the assembly from one assembler to another. Should be easy, right? Building It The first place zasm bailed was on the very first Forth word defined in the source code, EXIT. in file camel80.asm: 210: IF .NOT.(docode=DOCODE) ^ condition not evaluatable in pass1 That word expanded a macro, head, which itself expanded into a conditional define – an #ifndef, if you will. head MACRO #label,#length,#name,#action DW link DB 0 link DEFL $ DB #length,'#name' #label: IF .NOT.(#action=DOCODE) call #action ENDIF ENDM [..] ;C EXIT -- exit a colon definition head EXIT,4,EXIT,docode ld e,(ix+0) ; pop old IP from ret stk inc ix ld d,(ix+0) inc ix next It looks like the last argument of the macro invocation, docode, is meant to signal that the word is implemented as an alternative assembly-language routine, rather than to consider it “as pure Forth.” In other words, it’s thunking out to native code. Many of the words in CamelForth are defined as docode, which would then match the DOCODE define at assembly-time, assembled into the binary, and then be called when that word is requested at runtime. Others will use the dovar or docolon handlers, which respectively define a Forth variable or composes a Forth word from other Forth words. docode has no special implementation and just falls right through to the rest of the assembly language defined in the word. The exact way it’s implemented doesn’t really matter to me, but it seems that zasm does its passes differently than z80mr does, and is complaining about it. It does not want to expand the macro and evaluate the macro that came out of that macro on the same pass. Quoth the zasm documentation: ‘if’ starts a block of assembler instructions, which is only assembled if the given is true. The must be evaluatable in pass 1. Conditional assembly may be nested. note: this may change. […] Normally the assembler directives with ‘#’ should be used. Except that ‘if’ and ‘endif’ can occur in macros and the expanded macro can conditionally exclude some code.