PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/README.md

https://gitlab.com/nebogeo/co2
Markdown | 619 lines | 404 code | 215 blank | 0 comment | 0 complexity | dd5208461fbbf911c7355daa4533e210 MD5 | raw file
  1. # co2: Lisp on NES/Famicom
  2. A lisp for building NES/Famicom games.
  3. ![](shot.png)
  4. More impressive screenshots to follow.
  5. Generates 6502 assembly tested with [asm6](https://github.com/freem/asm6f) and [nestopia](http://nestopia.sourceforge.net/).
  6. Quite close to asm, no garbage collection, variables and direct memory
  7. peek/poke only. Supports decent recursion compatible function calling
  8. with arguments stored on the stack. Comes with NES specific calls for
  9. sprites, backgrounds and sound as used in a game.
  10. 16bit base addresses are generally specified via constants or
  11. predefined registers with 8 bit offsets. Some 16bit functionality, but
  12. not fully there yet.
  13. Small example code, for reading the joypad button state:
  14. (defun (read-joypad)
  15. ;; need to 'strobe' register before reading
  16. (set! reg-joypad-0 1)
  17. (set! reg-joypad-0 0)
  18. ;; need to read multiple times until we get to the button we want
  19. ;; functions implicitly return the result of the last expression
  20. (loop n 0 8
  21. (poke! pad-data n (and (peek reg-joypad-0) #x1))))
  22. (defun (pressed key)
  23. (peek pad-data key))
  24. (defun (update-sprite)
  25. ;; start sprite data dma to the oam
  26. (set! reg-oam-dma sprite-dma)
  27. (read-joypad)
  28. ;; control a range of sprites with the joypad
  29. (cond
  30. ((pressed joypad-up) (sub-sprites-y! 0 4 1))
  31. ((pressed joypad-down) (add-sprites-y! 0 4 1))
  32. ((pressed joypad-left) (sub-sprites-x! 0 4 1))
  33. ((pressed joypad-right) (add-sprites-x! 0 4 1))
  34. (else 0))
  35. See example.co2 for more of this.
  36. ## Quick start
  37. Requires racket, asm6 and nestopia
  38. $ ./build.sh example/example.co2
  39. ## Memory use
  40. $000 - $0fa : defvar reserves it's addresses here
  41. $0fc : random state register
  42. $0fd : stack frame low
  43. $0fe : stack frame high
  44. $0ff : compiler working register
  45. $100 - $1ff : stack
  46. $200 - $2ff : sprite control data
  47. $300 - ... : free
  48. ## Fundamental stuff
  49. General purpose 6502 code (so might work on c64 etc?). All numbers
  50. follow racket conventions in terms of representation: `38` is decimal,
  51. `#xff` is hex, `#b11011011` is binary.
  52. ### (defvar name value)
  53. define and initialise a variable:
  54. (defvar num-poodles 45)
  55. ### (defun (name args ...) code)
  56. defines a function:
  57. (deffun (square x) (* x x))
  58. ### (defint (name) code)
  59. defines a function used as an interrupt handler (ends with "rts" opcode)
  60. (defint (vblank)
  61. (do-game-things))
  62. ### (defconst name value)
  63. defines a constant, compiler only - so no associated memory overhead
  64. (defconst sprite-data "$200") ;; where the sprite control data is
  65. ### (if expr true-expr false-expr)
  66. if this then that else the other
  67. (if (pressed joypad-up)
  68. (set! up 1)
  69. (set! up 0))
  70. as a proper scheme it returns it's expression result, so you can also do this:
  71. (set! up (if (pressed joypad-up) 1 0))
  72. ### (when expr true-expr-list)
  73. if this then that
  74. (when (pressed joypad-up)
  75. (go-up))
  76. ### (cond ((expr then-expr-list) ... (else else-expr-list)))
  77. helper to concatenate ifs (implemented as a macro)
  78. (cond
  79. ((pressed joypad-up) (go-up)
  80. ((pressed joypad-down) (go-down))
  81. ((pressed joypad-left) (go-left))
  82. ((pressed joypad-right) (go-right))
  83. (else 0))
  84. also returns the expression result so:
  85. (set! direction
  86. (cond
  87. ((pressed joypad-up) 1)
  88. ((pressed joypad-down) 2)
  89. ((pressed joypad-left) 3)
  90. ((pressed joypad-right) 4)
  91. (else 0))
  92. works...
  93. ### (eq? a b)
  94. returns true (1) if two bytes are equal otherwise returns false (0):
  95. (when (eq? (sprite-x player-sprite) 100)
  96. (do-something))
  97. ### (< a b)
  98. returns true (1) if a byte is less than another otherwise returns false
  99. (0):
  100. (when (< (sprite-x player-sprite) 100)
  101. (do-something))
  102. ### (> a b)
  103. returns true (1) if a byte is greater than another otherwise returns
  104. false (0):
  105. (when (> (sprite-x player-sprite) 100)
  106. (do-something))
  107. ### (<= a b)
  108. returns true (1) if a byte is less than or equal to another otherwise
  109. returns false (0):
  110. (when (<= (sprite-x player-sprite) 100)
  111. (do-something))
  112. ### (<s a b)
  113. signed version of <
  114. ### (>s a b)
  115. signed version of >
  116. ### (<=s a b)
  117. signed version of <=
  118. ### (not a)
  119. returns true if given false or vice versa
  120. (when (not (> (sprite-x player-sprite) 100))
  121. (do-something))
  122. ### (loop index-variable start end expr-list)
  123. a fairly optimal loop construct for ranges (much faster than while)
  124. (defvar n 0)
  125. (loop n 0 10
  126. (set-sprite-x! n 100))
  127. ### (while expr expr-list)
  128. general purpose looping
  129. (defvar n 0)
  130. (while (< n 10)
  131. (set-sprite-x! n (* n 10))
  132. (inc n))
  133. ### (do expr-list)
  134. collect a bunch of expression together, returns result of the last one
  135. (do
  136. (something)
  137. (something-else))
  138. ### (asm assembly-string)
  139. insert raw assembly code
  140. (asm
  141. ".byte \"NES\",$1a" ;; number of prg-rom blocks
  142. ".byte $01" ;; number of chr-rom blocks
  143. ".byte $01" ;; rom control bytes: horizontal mirroring, no sram or trainer, mapper #0
  144. ".byte $00,$00" ;; filler
  145. ".byte $00,$00,$00,$00,$00,$00,$00,$00")
  146. ### (byte data)
  147. insert raw bytes into the PRG-ROM
  148. (asm "palette:")
  149. (byte "$0d,$00,$00,...")
  150. ### (text string)
  151. insert raw text into the PRG-ROM
  152. (asm "mystring:")
  153. (text "hello world")
  154. ### (set! variable value)
  155. assignments
  156. (defvar num-poodles 0)
  157. (set! num-poodles 100)
  158. ### (poke! base-addr [offset] value)
  159. write to memory. base address can be a const or register 16bit address,
  160. offset and value are normal 8 bit expressions. offset is optional,
  161. reduces instruction count if you don't need it.
  162. ;; store button status
  163. (loop n 0 8
  164. (poke! pad-data n (and (peek reg-joypad-0) #x1))
  165. ### (peek base-addr [offset])
  166. returns the contents of memory at the specified address. offset is optional, reduces instruction count if you don't need it.
  167. ;; store button status
  168. (loop n 0 8
  169. (poke! pad-data n (and (peek reg-joypad-0) #x1))
  170. ### (+ a b)
  171. 8 bit addition with carry
  172. (+ (- 1 2) (* 2 3) 8)
  173. ### (- a b)
  174. 8 bit subtraction
  175. (+ (- 1 2) (* 2 3) 8)
  176. ### (* a b)
  177. 8 bit multiplication
  178. (+ (- 1 2) (* 2 3) 8)
  179. ### (and a b)
  180. 8 bit and - can be used for masking or logical operations.
  181. (when (and (peek reg-joypad-0) #x1)
  182. (do-something))
  183. ### (or a b)
  184. 8 bit or - can be used for masking or logical operations.
  185. (when (or (pressed joypad-a) (pressed joypad-b))
  186. (do-something))
  187. ### (xor a b)
  188. 8 bit xor (eor in asm) - can be used for bit flipping, eg:
  189. (define (toggle-bit-zero a)
  190. (xor a #b00000001))
  191. ### (inc a)
  192. increment a variable by one - maps to a single instruction
  193. (defvar n 0)
  194. (while (< n 10)
  195. (set-sprite-x! n (* n 10))
  196. (inc n))
  197. ## (dec a)
  198. decrement a variable by one - maps to a single instruction
  199. (defvar n 20)
  200. (while (> n 10)
  201. (set-sprite-x! n (* n 10))
  202. (dec n))
  203. ## (<< a num-bits)
  204. 8 bit left shift
  205. (defvar num-poodles 10)
  206. (set! num-poodles (<< num-poodles 2)) ;; 40 = 10*4
  207. ## (>> a num-bits)
  208. 8 bit right shift
  209. (defvar num-poodles 10)
  210. (set! num-poodles (>> num-poodles 1)) ;; 5 = 10/2
  211. # 16 bit commands
  212. some stuff to simplify 16bit operations - very unpolished
  213. ### (set16! variable value)
  214. a special two byte assignment from 16bit address label
  215. ;; must be contiguous
  216. (defvar addr-l 0)
  217. (defvar addr-h 0)
  218. (set16! addr-l mydata)
  219. ...
  220. (asm "mydata:")
  221. (bytes "0,1,2,3,4,5")
  222. ### (peek16 addr-l offset)
  223. returns memory at address specified by two bytes
  224. (defvar addr-l 0)
  225. (defvar addr-h 0)
  226. (set16! addr-l mydata)
  227. (peek16 addr-l 4) ;; returns 4
  228. ...
  229. (asm "mydata:")
  230. (bytes "0,1,2,3,4,5")
  231. ### (high label)
  232. returns the high byte of an address
  233. (set! addr-h (high mydata))
  234. ...
  235. (asm "mydata:")
  236. (bytes "0,1,2,3,4,5")
  237. ### (low label)
  238. returns the low byte of an address
  239. (set! addr-l (low mydata))
  240. ...
  241. (asm "mydata:")
  242. (bytes "0,1,2,3,4,5")
  243. ### (+16! val-h h l)
  244. two byte in-place addition for 16bit maths
  245. (defvar h 0)
  246. (defvar l 255)
  247. (+16! h 0 1)
  248. ;; h is now 1, l is 0
  249. ### (-16! val-h h l)
  250. two byte in-place subtraction for 16bit maths
  251. (defvar h 1)
  252. (defvar l 9)
  253. (-16! h 0 10)
  254. ;; h is now 0, l is 255
  255. # Experimental
  256. NES/Famicom specific commands. These are subject to much change, while
  257. we figure out how the architecture works and the best approach to game
  258. programming.
  259. ## (init-system)
  260. clears memory, resets stack pointer and stack frame, initialises
  261. random number generator etc. needs to be called at the start of
  262. your reset interrupt.
  263. ## (wait-vblank)
  264. delay for a vblank
  265. (wait-vblank)
  266. ## (org addr)
  267. set location of following code/data
  268. (org #xc000)
  269. ## (memset address value)
  270. block writes an entire page of PRG-RAM - 256 bytes to a 16bit address
  271. ;; clear sprite data
  272. (memset sprite-data 0)
  273. ## PPU DMA commands
  274. ## (ppu-memset base-ppuaddr ppu-offset-h ppu-offset-l length value)
  275. block writes a single value into ppu memory
  276. ;; write a load of tile ids to background memory
  277. (ppu-memset ppu-name-table-1 0 0 #x2f tile-id 0)
  278. note: should only be called when the ppu is disabled or at the start
  279. of vblank.
  280. ## (ppu-memcpy base-ppuaddr ppu-offset-h ppu-offset-l prg-end-offset prg-addr prg-start-offset)
  281. copy a load of prg bytes to the ppu. dst and src addresses need to be 16bit constants/registers.
  282. ;; load a palette
  283. (asm "palette: .incbin \"example.pal\"")
  284. ...
  285. ;; copy all 32bytes of bg/sprite palette into the ppu
  286. (ppu-memcpy ppu-palette 0 0 #x20 palette 0))
  287. ## OAM commands
  288. these commands write into sprite shadow ram, which is dma-ed to the
  289. PPU every frame
  290. ### (set-sprite-x! sprite-id val)
  291. ### (set-sprite-y! sprite-id val)
  292. ### (set-sprite-id! sprite-id val)
  293. ### (set-sprite-attr! sprite-id val)
  294. sets sprite values for the specified sprite
  295. ### (get-sprite-x sprite-id)
  296. ### (get-sprite-y sprite-id)
  297. ### (get-sprite-id sprite-id)
  298. ### (get-sprite-attr sprite-id)
  299. gets sprite values for the specified sprite
  300. ### (get-sprite-vflip sprite-id)
  301. ### (get-sprite-hflip sprite-id)
  302. ### (set-sprite-vflip sprite-id value)
  303. ### (set-sprite-hflip sprite-id value)
  304. macros for setting v/hflip on a sprite
  305. ## multiple sprite handling
  306. often we are dealing with large collections of sprites, or metasprites.
  307. these commands optimise for quickly dealing with contiguous groups of
  308. sprites (see below for 2x2 metasprite commands)
  309. ### (add-sprites-x! sprite-id sprite-count value)
  310. ### (add-sprites-y! sprite-id sprite-count value)
  311. ### (sub-sprites-x! sprite-id sprite-count value)
  312. ### (sub-sprites-y! sprite-id sprite-count value)
  313. add or subtract from the current sprite location
  314. ### (or-sprites-attr sprite-id sprite-count value)
  315. binary or-s the value to the current set of sprites
  316. ## 2x2 metasprite handling
  317. the most common size of sprites are 2x2 square, these commands produce
  318. code optimised for this type of metasprite
  319. ## (animate-sprites-2x2! sprite-id pattern-location)
  320. sets the 4 sprite metasprite starting at sprite-id to the patterns
  321. starting at pattern-location. the patterns are arranged to form a
  322. visible block for the sprite pattern data in a 16x16 grid for easier
  323. drawing/editing. the first is the top left, second top right
  324. (pattern-location+1), third bottom left (pattern-location+16), fourth
  325. bottom right (pattern-location+17).
  326. ## (set-sprites-2x2-x! sprite-id x-value)
  327. sets the top left x coordinate of the metasprite, keeping them 'stuck'
  328. together.
  329. ## (set-sprites-2x2-y! sprite-id y-value)
  330. sets the top left y coordinate of the metasprite, keeping them 'stuck'
  331. together.
  332. # registers
  333. these are defined as constants for your convenience and enjoyment.
  334. ## ppu/oam registers
  335. - reg-ppu-ctl
  336. - reg-ppu-mask
  337. - reg-ppu-status
  338. - reg-oam-addr
  339. - reg-oam-data
  340. - reg-ppu-scroll
  341. - reg-ppu-addr
  342. - reg-ppu-data
  343. - reg-oam-dma
  344. ### apu registers
  345. - reg-apu-pulse1-control
  346. - reg-apu-pulse1-ramp
  347. - reg-apu-pulse1-ft
  348. - reg-apu-pulse1-ct
  349. - reg-apu-pulse2-control
  350. - reg-apu-pulse2-ramp
  351. - reg-apu-pulse2-ft
  352. - reg-apu-pulse2-ct
  353. - reg-apu-tri-control
  354. - reg-apu-tri-ft
  355. - reg-apu-tri-ct
  356. - reg-apu-noise-env
  357. - reg-apu-noise-ft
  358. - reg-apu-noise-ct
  359. - reg-apu-dmc-control
  360. - reg-apu-dmc-dac
  361. - reg-apu-dmc-addr
  362. - reg-apu-dmc-size
  363. - reg-apu-channel
  364. ### input
  365. - reg-joypad-0
  366. - reg-joypad-1
  367. - joypad-a
  368. - joypad-b
  369. - joypad-select
  370. - joypad-start
  371. - joypad-up
  372. - joypad-down
  373. - joypad-left
  374. - joypad-right
  375. ### ppu vram addresses
  376. - ppu-name-table-0
  377. - ppu-attr-table-0
  378. - ppu-name-table-1
  379. - ppu-attr-table-1
  380. - ppu-name-table-2
  381. - ppu-attr-table-2
  382. - ppu-name-table-3
  383. - ppu-attr-table-3
  384. - ppu-palette
  385. - ppu-bg-palette
  386. - ppu-sprite-palette
  387. # program structure
  388. follows normal NES/Famicom behaviour
  389. (do
  390. NES header stuff...
  391. (org #xc000) ;; code start
  392. (defun ...)
  393. (defun ...)
  394. ...
  395. (defint (vblank) ...)
  396. (defint (reset)
  397. (init-system)
  398. ...)
  399. (defint (irq) ...)
  400. data
  401. ;; set up the interrupt vectors
  402. (asm ".word vblank, reset, irq")
  403. more data
  404. )