Skip to content

Instantly share code, notes, and snippets.

@sylefeb
Last active December 2, 2025 16:09
Show Gist options
  • Select an option

  • Save sylefeb/2f25a84c76184970fddb10143f0bcea0 to your computer and use it in GitHub Desktop.

Select an option

Save sylefeb/2f25a84c76184970fddb10143f0bcea0 to your computer and use it in GitHub Desktop.
step3.si with sound generator
// SL 2022-01-10 @sylefeb
// https://github.com/sylefeb/Silice
// MIT license, see LICENSE_MIT in Silice repo root
// Pre-compilation script, embeds compiled code within a string
// Code has to be compiled into firmware/code.hex before
$$dofile('pre_include_compiled.lua')
// Setup memory size
// - addrW is the address bus width
// - the topmost bit is used to indicate peripheral access
// - we thus allocate 2^(addrW-1) uint32 of RAM
$$addrW = 15
$$periph_bit = addrW-1
// Configure BRAM (needed for write mask)
$$config['bram_wmask_byte_wenable_width'] = 'data'
// Includes the processor
$include('../../../projects/ice-v/CPUs/ice-v.si')
// Includes the SPIscreen driver
$$ OLED_SLOW=1
$include('../../../projects/ice-v/SOCs/ice-v-oled.si')
// Memory interface between SOC and CPU
group mem_io
{
uint4 wenable(0), // write enable mask (xxxx, 0:nop, 1:write)
uint32 wdata(0), // data to write
uint32 rdata(0), // data read from memory
uint$addrW$ addr(0), // address, init is boot address
}
// --------------------------------------------------
// Sound generator
// --------------------------------------------------
unit sndgen(input uint24 inc_per_cycle,input uint8 amplitude,
output uint8 audio)
{
uint25 cnt = 0;
always {
uint32 adjusted = (cnt[0,24] * amplitude) >> 8;
audio = adjusted[16,8];
if (amplitude != 0) {
// __display("P:%d A:%d",inc_per_cycle,amplitude);
__display("cnt:%b",cnt);
}
if (cnt > (1<<24)) {
cnt = 0;
} else {
cnt = cnt + inc_per_cycle;
}
}
}
// --------------------------------------------------
// SOC unit (main)
// --------------------------------------------------
// some input/outputs do not exist in simulation and
// are therefore enclosed in pre-processor conditions
unit main(
output uint8 leds,
$$if BUTTONS then
input uint7 btns,
$$end
$$if AUDIO then
output uint4 audio_l,
output uint4 audio_r,
$$end
output uint1 oled_clk,
output uint1 oled_mosi,
output uint1 oled_dc,
output uint1 oled_resn,
output uint1 oled_csn(0),
$$if VERILATOR then
// configuration for SPIscreen simulation
output uint2 spiscreen_driver(1/*SSD1351*/),
output uint10 spiscreen_width(128),
output uint10 spiscreen_height(128),
$$end
$$if SDCARD then
output uint1 sd_clk,
output uint1 sd_csn,
output uint1 sd_mosi,
input uint1 sd_miso,
$$end
) {
$$if SIMULATION then
// count cycles in simulation for debugging purposes
uint32 cycle(0);
$$end
// SPIscreen (OLED) controller chip
oled display(
oled_din :> oled_mosi,
oled_clk :> oled_clk,
oled_dc :> oled_dc,
);
$$if not SDCARD then
// for simulation ('fake' inputs/outputs)
uint1 sd_clk(0);
uint1 sd_csn(0);
uint1 sd_mosi(0);
uint1 sd_miso(0);
$$end
$$if not AUDIO then
// for simulation ('fake' inputs/outputs)
uint4 audio_l(0);
uint4 audio_r(0);
$$end
// Sound generator
sndgen sg;
// RAM
// Instantiate the memory interface
mem_io memio;
// Instantiate a BRAM holding the system's RAM, 32bits words
// -> uses template "bram_wmask_byte", that turns wenable into a byte mask
bram uint32 ram<"bram_wmask_byte">[$1<<(addrW-1)$] = $meminit$;
// Instantiate our CPU
rv32i_cpu cpu( mem <:> memio );
// Variables to record previous cycle CPU access (peripherals memory mapping)
// The CPU issues a memory request a cycle i and expects the result at i+1
uint$addrW$ prev_mem_addr(0);
uint4 prev_mem_rw(0);
uint32 prev_wdata(0);
// --- SOC logic, the always block is always active
always {
display.enable = 0; // maintain display enable low (pulses on use)
// ---- check whether the CPU read from or wrote to a peripheral address
uint1 peripheral = prev_mem_addr[$periph_bit$,1];
uint1 peripheral_r = peripheral & (prev_mem_rw == 4b0); // reading periph.
uint1 peripheral_w = peripheral & (prev_mem_rw != 4b0); // writing periph.
uint1 audio_access = prev_mem_addr[$periph_bit-1$,1];
uint1 leds_access = prev_mem_addr[ 0,1];
uint1 display_direct_access = prev_mem_addr[ 1,1];
uint1 display_reset_access = prev_mem_addr[ 2,1];
uint1 sd_access = prev_mem_addr[ 5,1];
uint1 sndgen_access = prev_mem_addr[ 7,1];
// ---- memory access CPU <-> BRAM (reads and writes)
// reads RAM, peripherals => CPU
memio.rdata = // read data is either from memory or SOC peripherals
// CPU reading from RAM
(~peripheral_r ? ram.rdata : 32b0)
// CPU reading from peripherals
| ((peripheral_r & sd_access) ? {31b0, sd_miso} : 32b0)
;
// writes CPU => RAM
ram.wenable = memio.wenable & {4{~memio.addr[$periph_bit$,1]}};
// ^^^^^^^ no write if on peripheral addresses
ram.wdata = memio.wdata;
ram.addr = memio.addr;
// TEST: sound generator to audio
audio_l = sg.audio[4,4];
audio_r = sg.audio[4,4];
// writes CPU => peripherals
if (peripheral_w) {
/// LEDs
leds = leds_access ? prev_wdata[0,8] : leds;
/// sopund generator
sg.inc_per_cycle = sndgen_access ? prev_wdata[8,24] : sg.inc_per_cycle;
sg.amplitude = sndgen_access ? prev_wdata[0, 8] : sg.amplitude;
/// display
if (display_direct_access) {
// -> whether to send command or data
display.enable = (prev_wdata[9,1] | prev_wdata[10,1]);
// -> byte to send
display.byte = prev_wdata[0,8];
// -> data or command
display.data_or_command = prev_wdata[10,1];
}
// -> SPIscreen reset
oled_resn = ~ (display_reset_access & prev_wdata[0,1]);
/// sdcard output pins
sd_clk = sd_access ? prev_wdata[0,1] : sd_clk;
sd_mosi = sd_access ? prev_wdata[1,1] : sd_mosi;
sd_csn = sd_access ? prev_wdata[2,1] : sd_csn;
/// audio
// if (audio_access) {audio_l = prev_wdata[0,8]; audio_r = prev_wdata[0,8];}
$$if SIMULATION then
// Add some simulation debug output here, convenient during development!
if (leds_access) {
__display("[cycle %d] LEDs: %b (%d)",cycle,leds,prev_wdata);
if (leds == 255) { __finish(); }// special LED value stops simulation
// convenient to interrupt from firmware
}
if (audio_access) {
__display("[cycle %d] audio sample %x",cycle,audio_l);
}
$$end
}
// record current access for next cycle memory mapping checks
prev_mem_addr = memio.addr;
prev_mem_rw = memio.wenable;
prev_wdata = memio.wdata;
$$if SIMULATION then
cycle = cycle + 1;
$$end
} // end of always block
}
// --------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment