Skip to content

Instantly share code, notes, and snippets.

@JetForMe
Created December 10, 2022 15:10
Show Gist options
  • Save JetForMe/0d2fa9d6f464bdce6291a7e0b226b94e to your computer and use it in GitHub Desktop.
Save JetForMe/0d2fa9d6f464bdce6291a7e0b226b94e to your computer and use it in GitHub Desktop.
Ben Eater CPU Microcode ROM Generator
#!/usr/bin/env swift sh
/**
This Swift script requires that you have [swift-sh](https://github.com/mxcl/swift-sh) installed:
```
$ brew install swift-sh
```
My CPU differs from Bean Eater’s, in that I program two different ROMs,
and both are addressed identically. I think in the future I will change
that, but instead of tying one A7 line high and the other low, I’ll
tie A0 low on the A ROM and high on the B rom, and shift all the other
wires up one bit. This *should* let me write a single ROM file with
the two bytes of the microcode word adjacent to each other.
*/
import Foundation
import ArgumentParser // apple/swift-argument-parser ~> 1.2
import Path // mxcl/Path.swift ~> 1.4.0
struct
ControlLines : OptionSet
{
static let hlt = ControlLines(rawValue: 0b1000_0000_0000_0000)
static let mi = ControlLines(rawValue: 0b0100_0000_0000_0000)
static let ri = ControlLines(rawValue: 0b0010_0000_0000_0000)
static let ro = ControlLines(rawValue: 0b0001_0000_0000_0000)
static let io = ControlLines(rawValue: 0b0000_1000_0000_0000)
static let ii = ControlLines(rawValue: 0b0000_0100_0000_0000)
static let ai = ControlLines(rawValue: 0b0000_0010_0000_0000)
static let ao = ControlLines(rawValue: 0b0000_0001_0000_0000)
static let eo = ControlLines(rawValue: 0b0000_0000_1000_0000)
static let su = ControlLines(rawValue: 0b0000_0000_0100_0000)
static let bi = ControlLines(rawValue: 0b0000_0000_0010_0000)
static let oi = ControlLines(rawValue: 0b0000_0000_0001_0000)
static let ce = ControlLines(rawValue: 0b0000_0000_0000_1000)
static let co = ControlLines(rawValue: 0b0000_0000_0000_0100)
static let j = ControlLines(rawValue: 0b0000_0000_0000_0010)
let rawValue : UInt16
}
/**
Note that op codes are only four bits (a maximum of 16 can be defined,
and their values must be <= 0b1111).
*/
enum
OpCode : UInt8
{
case nop = 0b0000
case lda = 0b0010
case ldb = 0b0011
case add = 0b0100
case sub = 0b0101
case jmp = 0b1000
case out = 0b1110
case halt = 0b1111
}
/**
We define instructions as a pairing of opcode and asserted control lines. The
initial fetch microcode is automatically inserted. So
*/
let
instructions: [OpCode : [ControlLines]] =
[
.nop : [],
.lda : [[.io, .mi], [.ro, .ai]],
.ldb : [[.io, .mi], [.ro, .bi]],
.add : [[.io, .mi], [.ro, .bi], [.eo, .ai]],
.sub : [[.io, .mi], [.ro, .bi, .su], [.eo, .ai, .su]], // TODO: Do we need to subtract on both steps?
.jmp : [[.io, .j]],
.out : [[.ao, .oi]],
.halt : [[.hlt]],
]
for (key, steps) in instructions
{
var allSteps: [ControlLines] = [[.mi, .co], [.ro, .ii, .ce]]
allSteps += steps
var s = String()
for step in allSteps
{
s += "\(String(format: "0x%04x", step.rawValue)), "
}
print("Inst \(key):\t\(s)")
}
// Build the full 128 words of microcode. For each possible
// opcode, append 8 words, but split the high and low bytes
// into separate buffers…
var romA = Data()
var romB = Data()
for opcodeValue: UInt8 in 0 ..< 16
{
// Always append the two fetch steps…
let step1: ControlLines = [.mi, .co]
let step2: ControlLines = [.ro, .ii, .ce]
romA.append(UInt8(step1.rawValue >> 8))
romB.append(UInt8(step1.rawValue & 0x0F))
romA.append(UInt8(step2.rawValue >> 8))
romB.append(UInt8(step2.rawValue & 0xFF))
// If this value has an opcode defined, append the instruction’s steps…
if let opcode = OpCode(rawValue: opcodeValue),
let steps = instructions[opcode]
{
print("Found opcode for \(opcodeValue)")
for step in steps
{
let highByte: UInt8 = UInt8(step.rawValue >> 8)
let lowByte: UInt8 = UInt8(step.rawValue & 0x00FF)
romA.append(highByte)
romB.append(lowByte)
}
// If necessary, fill out the 8 steps with zeros…
if steps.count < 6 // 6 because we generate the first two, so the steps array is only the last six.
{
romA.append(contentsOf: [UInt8](repeating: 0, count: 6 - steps.count))
romB.append(contentsOf: [UInt8](repeating: 0, count: 6 - steps.count))
}
}
else
{
// This opcode value doesn’t correspond to
// a defined instruction, so write all zeros…
romA.append(contentsOf: [UInt8](repeating: 0, count: 6))
romB.append(contentsOf: [UInt8](repeating: 0, count: 6))
}
}
print("ROM A size: \(romA.count)")
print("ROM B size: \(romB.count)")
// Append zeros to fill out the rom…
romA.append(contentsOf: [UInt8](repeating: 0, count: 2048 - romA.count))
romB.append(contentsOf: [UInt8](repeating: 0, count: 2048 - romB.count))
print("ROM A size: \(romA.count)")
print("ROM B size: \(romB.count)")
do
{
try romA.write(to: Path.cwd / "romA.bin", atomically: true)
try romB.write(to: Path.cwd / "romB.bin", atomically: true)
}
catch let e
{
print("Error writing ROM files: \(e)")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment