const RA: u32 = 1;
const T0: u32 = 5;
const T1: u32 = 6;
const T2: u32 = 7;
const A0: u32 = 10;
const A1: u32 = 11;
const A2: u32 = 12;
const A3: u32 = 13;
const A4: u32 = 14;
const A5: u32 = 15;
const ZERO: u32 = 0;
const OP_LUI: u32 = 0b011_0111;
const OP_JAL: u32 = 0b110_1111;
const OP_JALR: u32 = 0b110_0111;
const OP_BRANCH: u32 = 0b110_0011;
const OP_LOAD: u32 = 0b000_0011;
const OP_STORE: u32 = 0b010_0011;
const OP_IMM: u32 = 0b001_0011;
const OP_REG: u32 = 0b011_0011;
const fn i_type(imm: i32, rs1: u32, funct3: u32, rd: u32, opcode: u32) -> u32 {
((imm as u32 & 0xfff) << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode
}
const fn s_type(imm: i32, rs2: u32, rs1: u32, funct3: u32, opcode: u32) -> u32 {
let imm = imm as u32;
(((imm >> 5) & 0x7f) << 25)
| (rs2 << 20)
| (rs1 << 15)
| (funct3 << 12)
| ((imm & 0x1f) << 7)
| opcode
}
const fn b_type(imm: i32, rs2: u32, rs1: u32, funct3: u32, opcode: u32) -> u32 {
let imm = imm as u32;
(((imm >> 12) & 1) << 31)
| (((imm >> 5) & 0x3f) << 25)
| (rs2 << 20)
| (rs1 << 15)
| (funct3 << 12)
| (((imm >> 1) & 0xf) << 8)
| (((imm >> 11) & 1) << 7)
| opcode
}
const fn u_type(imm: u32, rd: u32, opcode: u32) -> u32 {
(imm << 12) | (rd << 7) | opcode
}
const fn j_type(imm: i32, rd: u32, opcode: u32) -> u32 {
let imm = imm as u32;
(((imm >> 20) & 1) << 31)
| (((imm >> 1) & 0x3ff) << 21)
| (((imm >> 11) & 1) << 20)
| (((imm >> 12) & 0xff) << 12)
| (rd << 7)
| opcode
}
const fn lui(rd: u32, imm: u32) -> u32 {
u_type(imm, rd, OP_LUI)
}
const fn addi(rd: u32, rs1: u32, imm: i32) -> u32 {
i_type(imm, rs1, 0b000, rd, OP_IMM)
}
const fn andi(rd: u32, rs1: u32, imm: i32) -> u32 {
i_type(imm, rs1, 0b111, rd, OP_IMM)
}
const fn xor(rd: u32, rs1: u32, rs2: u32) -> u32 {
(rs2 << 20) | (rs1 << 15) | (0b100 << 12) | (rd << 7) | OP_REG
}
const fn lw(rd: u32, rs1: u32, imm: i32) -> u32 {
i_type(imm, rs1, 0b010, rd, OP_LOAD)
}
const fn sw(rs2: u32, rs1: u32, imm: i32) -> u32 {
s_type(imm, rs2, rs1, 0b010, OP_STORE)
}
const fn sb(rs2: u32, rs1: u32, imm: i32) -> u32 {
s_type(imm, rs2, rs1, 0b000, OP_STORE)
}
const fn bne(rs1: u32, rs2: u32, offset: i32) -> u32 {
b_type(offset, rs2, rs1, 0b001, OP_BRANCH)
}
const fn jal(rd: u32, offset: i32) -> u32 {
j_type(offset, rd, OP_JAL)
}
const fn ret() -> u32 {
i_type(0, RA, 0b000, ZERO, OP_JALR)
}
const fn li_hi(rd: u32, value: u32) -> u32 {
lui(rd, (value.wrapping_add(0x800) >> 12) & 0xf_ffff)
}
const fn li_lo(rd: u32, value: u32) -> u32 {
let low = (value & 0xfff) as i32;
let low = if low >= 0x800 { low - 0x1000 } else { low };
addi(rd, rd, low)
}
const SPI: u32 = 0xf000_0000;
const LCDC: u32 = 0xf000_1000;
const FB: u32 = 0x1000_0000;
const WIDTH: i32 = 320;
const HEIGHT: i32 = 240;
const SEND: i32 = 1;
const WAIT: i32 = 5;
const MAIN: i32 = 11;
const ROW: i32 = 35;
const COL: i32 = 37;
const DONE: i32 = 67;
const fn rel(from: i32, to: i32) -> i32 {
(to - from) * 4
}
const PROGRAM: [u32; DONE as usize + 1] = [
jal(ZERO, rel(0, MAIN)),
li_hi(A0, 1),
li_lo(A0, 1),
sw(A0, T0, 8),
sw(A1, T0, 16),
lw(A0, T0, 12),
andi(A0, A0, 1),
bne(A0, ZERO, rel(7, WAIT)),
lw(A0, T0, 16),
sw(ZERO, T0, 8),
ret(),
li_hi(T0, SPI),
li_lo(T0, SPI),
li_hi(T1, LCDC),
li_lo(T1, LCDC),
li_hi(T2, FB),
li_lo(T2, FB),
li_hi(A0, 0x0f01),
li_lo(A0, 0x0f01),
sw(A0, T0, 0),
li_hi(A0, 3),
li_lo(A0, 3),
sw(A0, T0, 4),
li_hi(A1, 0x1009),
li_lo(A1, 0x1009),
jal(RA, rel(25, SEND)),
li_hi(A1, 0x1180),
li_lo(A1, 0x1180),
jal(RA, rel(28, SEND)),
li_hi(A1, 0x1440),
li_lo(A1, 0x1440),
jal(RA, rel(31, SEND)),
li_hi(A2, 0),
li_lo(A2, 0),
addi(A4, T2, 0),
li_hi(A3, 0),
li_lo(A3, 0),
sb(A3, A4, 0),
sb(A2, A4, 1),
xor(A5, A3, A2),
sb(A5, A4, 2),
addi(A4, A4, 3),
addi(A3, A3, 1),
li_hi(A5, WIDTH as u32),
li_lo(A5, WIDTH as u32),
bne(A3, A5, rel(45, COL)),
addi(A2, A2, 1),
li_hi(A5, HEIGHT as u32),
li_lo(A5, HEIGHT as u32),
bne(A2, A5, rel(49, ROW)),
sw(T2, T1, 4),
li_hi(A0, 0),
li_lo(A0, 0),
sw(A0, T1, 8),
sw(A0, T1, 12),
li_hi(A0, WIDTH as u32),
li_lo(A0, WIDTH as u32),
sw(A0, T1, 16),
li_hi(A0, HEIGHT as u32),
li_lo(A0, HEIGHT as u32),
sw(A0, T1, 20),
li_hi(A0, 0),
li_lo(A0, 0),
sw(A0, T1, 24),
li_hi(A0, 1),
li_lo(A0, 1),
sw(A0, T1, 0),
jal(ZERO, 0),
];
pub const PANEL_DEMO: &[u8] = &{
let mut out = [0u8; (DONE as usize + 1) * 4];
let mut i = 0;
while i < PROGRAM.len() {
let word = PROGRAM[i];
out[i * 4] = word as u8;
out[i * 4 + 1] = (word >> 8) as u8;
out[i * 4 + 2] = (word >> 16) as u8;
out[i * 4 + 3] = (word >> 24) as u8;
i += 1;
}
out
};
#[must_use]
pub const fn demo_pixel(x: u32, y: u32) -> [u8; 3] {
[x as u8, y as u8, (x ^ y) as u8]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_label_indices_match_the_program() {
assert_eq!(PROGRAM[SEND as usize] & 0x7f, OP_LUI, "send starts with li");
assert_eq!(
PROGRAM[WAIT as usize] & 0x7f,
OP_LOAD,
"wait starts with lw"
);
assert_eq!(PROGRAM[MAIN as usize] & 0x7f, OP_LUI, "main starts with li");
assert_eq!(PROGRAM[ROW as usize] & 0x7f, OP_LUI, "row starts with li");
assert_eq!(PROGRAM[COL as usize] & 0x7f, OP_STORE, "col starts with sb");
assert_eq!(PROGRAM[DONE as usize], jal(ZERO, 0), "done spins on itself");
assert_eq!(PANEL_DEMO.len(), PROGRAM.len() * 4);
}
#[test]
fn li_puts_the_value_a_two_instruction_sequence_would() {
for value in [
0u32,
1,
3,
0x7ff,
0x800,
0xfff,
0x1009,
0x0f01,
0xf000_0000,
0x1000_0000,
] {
let hi = li_hi(A0, value) >> 12;
let lo = (li_lo(A0, value) as i32) >> 20;
let built = (hi << 12).wrapping_add(lo as u32);
assert_eq!(built, value, "li a0, {value:#x}");
}
}
#[cfg(feature = "cpu-riscv")]
#[test]
fn the_program_disassembles_to_the_listing() {
use crate::cpu::riscv::disasm::disassemble_one;
use crate::cpu::riscv::isa::Xlen;
let text = |index: usize| -> alloc::string::String {
let addr = (index * 4) as u64;
disassemble_one(addr, Xlen::Rv32, &mut |at: u64| {
let word = PROGRAM.get((at / 4) as usize).copied()?;
Some(if at.is_multiple_of(4) {
word as u16
} else {
(word >> 16) as u16
})
})
.expect("the program disassembles")
.text
};
for (index, want) in [
(0, "jal zero, 0x2c"),
(SEND as usize + 2, "sw a0, 8(t0)"),
(SEND as usize + 3, "sw a1, 0x10(t0)"),
(WAIT as usize, "lw a0, 0xc(t0)"),
(WAIT as usize + 1, "andi a0, a0, 1"),
(WAIT as usize + 2, "bne a0, zero, 0x14"),
(SEND as usize + 9, "jalr zero, 0(ra)"),
(MAIN as usize, "lui t0, 0xfffffffff0000000"),
(COL as usize, "sb a3, 0(a4)"),
(COL as usize + 2, "xor a5, a3, a2"),
(DONE as usize, "jal zero, 0x10c"),
] {
assert_eq!(text(index), want, "instruction {index}");
}
}
}