From 95f19f4400b77e4008676731f34b1ea88d07054f Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Sat, 8 Feb 2025 22:55:01 +0100 Subject: Initial commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miquel Sabaté Solà --- .editorconfig | 13 ++ README.md | 517 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 530 insertions(+) create mode 100644 .editorconfig create mode 100644 README.md diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c420a82 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +charset = utf-8 +indent_size = 4 +insert_final_newline = true +end_of_line = lf + +[*.{s,S}] +indent_style = space + +[Makefile] +indent_style = tab diff --git a/README.md b/README.md new file mode 100644 index 0000000..20d9319 --- /dev/null +++ b/README.md @@ -0,0 +1,517 @@ +# Prelude + +This guide is not something too serious. It's not a guide meant to be enforced, +nor something to even be trusted. On the contrary, it's just a set of rules I +have been cooking up while hacking on the NES/Famicom. These are rules which I +believe that have made things easier for me on NES/Famicom development, but it's +not definitive. Hence, it's not complete and I can still be persuaded away from +things I say here. All in all, take all of this with a grain of salt and assume +that if I write something that looks fishy, maybe it's just that I don't know +any better and I might be able to be convinced via a [Github +issue](https://github.com/mssola/style.nes/issues). That is to say, discussions +are welcome, even if I can always just say no. + +Last but not least: get the know the NES/Famicom first! This is not a reference +guide nor a list of pitfalls that should be avoided. For all of this, just refer +to the [NESDev wiki](https://www.nesdev.org/wiki/). + +# Programming language + +There are multiple ways to code on the NES/Famicom. I have seen helpful and +insightful projects which have used C as a programming language. That being +said, the NES/Famicom is really scarce when it comes to resources. Hence, if you +can, you should try to make the most out of it and **write everything in MOS 6502 +assembly**. + +This is not a comment against C, but as clever and helpful as tools like +[cc65](https://github.com/cc65/cc65) can be, they can never quite reach the +level of optimization over what the machine is executing as assembly. Couple +that with the fact that MOS 6502 assembly is not that hard to learn, and that +most learning resources are also given in assembly. + +That being said, one good argument could be made that you could write the most +performance critical bits in assembly and leave the business code in C so it's +easier to understand or debug. That certainly is a possibility, but in the end +the mixing of both languages can go wrong in different and hidden ways, makes +building and linking more complex, and, in my humble opinion, for not too much +to gain. Hence, just stick with assembly and you should be fine. + +That's why for the rest of this guide we will be talking about assembly and +never about C or any other languages. + +# Source code layout + +## Encoding + +Use **UTF-8** as the source file encoding. For the code itself you should stick +to good ol' ASCII, but for comments and sharing your code around, just use UTF-8 +which is supported virtually everywhere. + +## Indentation, tabs vs spaces + +Use only spaces for indentation, no hard tabs. Each indentation level is 4 +spaces long. Hence: + +``` assembly +bad: + lda #1 + +good: + lda #1 +``` + +A new indentation level is to be given after the start of a function: + +``` assembly +function1: +lda #1 ; bad! + +function2: + lda #1 ; good! +``` + +Labels, though, are to be kept at the previous indentation level: + +``` assembly +function1: + lda #1 + @label: ; bad! + jmp @label + +function2: + lda #1 +@label: ; good! + jmp @label +``` + +The rationale for this is that labels ought to be clearly visible, and by being +at a different indentation level as instructions they certainly stick out. The +recommended `@` prefix for labels (see [Naming +conventions](#naming-conventions)) disambiguates with the name of the function +clearly as well. For a similar reason labels are to be put on their own line: + +``` assembly +bad: lda #1 +good: + lda #1 + +;; Yes, even data. +bad: .byte $02 +good: + .byte $02 +``` + +Introduce a new indentation level inside of `.proc`, `.macro`, `.repeat`, `.if` +and similar control statements which expect a block of code inside. Thus: + +``` assembly +.proc bad +lda #1 +.endproc + +.proc good + lda #1 +.endproc +``` + +One could argue that after a label we could introduce another indentation level. +That is certainly tolerable, but I have the following disagreements with this +approach: + +1. It artificially makes it look like a higher programming language (e.g. there + is no lexical scoping). +2. Having multiple indentation levels is already a code smell: avoid too much + complexity on your functions as you ought to be as performant as possible. + +## Line endings and the likes + +Let's get simple statements out of the way: + +- Limit lines to 80 characters. +- No trailing whitespace. +- Use Unix-style line endings. +- End each file with a newline. + +I will not bother to further explain on the above, as others have wasted more +time on this than me on these arguments. It can be easily configured through +your editor (and this repository also holds an [.editorconfig](./.editorconfig) +to help you on this). If your editor doesn't support some of this options, just +replace it. + +# Allocation conventions + +I am not going to reinvent the wheel here: just stick to the comments on the +[NESDev wiki](https://www.nesdev.org/wiki/CPU_memory_map), or the [sample RAM +map](https://www.nesdev.org/wiki/Sample_RAM_map) on how to allocate memory on +the NES/Famicom. + +In general, you should be very mindful when placing your data, and note that +because of the MOS 6502 architecture, there is a noteworthy difference between +placing data on the zero page or not. Hence, just to reiterate: ensure that the +data you use more often is placed on the zero page. Note that the [Calling +conventions](#calling-conventions) further reiterate on this fact. + +Mainly because of this, and in stark contrast to many other code bases, avoid +using the `.res` control statement for "variables". Hence: + +``` assembly +bad_var: + .res 1 + +good_var = $01 +``` + +Using the `.res` control statement has two main benefits: + +1. The compiler can enforce that you don't go over the capacity for a given + segment. +2. You can add/remove variables without too much hassle. + +But it also has its drawbacks: + +1. You don't know where data is placed. This is important when debugging, where + you have to watch for a specific address. Hence, you'd need to manually + compute anyways the address for a variable (multiple times if you have + added/removed variables since the last time), while for `good_var` you + already know where it is located. +2. The `.res` statement guarantees that the data will be zero'ed out (or with + the given optional fill value). This is not possible for variables, as + "memory" will not be a part of the ROM file (for obvious reasons). Hence, you + still need to take care of initializing these variables. By using the `.res` + statement you are being misleading on how things work. This is because the + `.res` statement is meant to be used for stuff that will actually appear on + the ROM file, not for "variables" in memory. + +All of that being said, the first benefit that we pointed out is not to be +overlooked, but it can arguably be achieved via tooling as well. On that note +I'm working on an "address sanitizer" in +[mssola/tools.nes](https://github.com/mssola/tools.nes) which could take care of +listing which slots are available or which slots have been already been taken. + +# Naming conventions + +Use `snake_case` everywhere. + +``` assembly +badThing: + .byte $01 + +good_thing: + .byte $01 +``` + +## Use the `@` prefix for named labels which affect the control flow + +Named labels which are part of the control flow are to use the `@` symbol as a +prefix for their names. This is in accordance to the style used virtually +everwhere, and it clearly denotes which labels are actually part of the control +flow. Thus, labels which reference a piece of data should not be prefixed with +`@`, but labels which are part of the flow of branching/jumping should be +prefixed accordingly. + +``` assembly +;; bad +loop: + ldx #0 + lda @data, x +@data: + .byte $00 + +;; good +@loop: + ldx #0 + lda data, x +data: + .byte $00 +``` + +## Use memory-aware prefixes for variables + +Just to reiterate over what was said on [Allocation +conventions](#allocation-conventions): be very mindful on where data is placed. +On this, the name of "variables" can also help out, and more so on code that is +accessing it but it's far from where it was initialized or declared. Hence, the +context might have been lost and you might not be fully aware on what kind of +data you are operating on. Hence, use the following prefixes: + +- `zp_` for variables on the zeropage. +- `wr_` for variables on "Working RAM". +- `m_` for the rest. + +Consider the following code: + +``` assembly +lda Creative Commons Attribution +4.0 International. -- cgit v1.2.3