Skip to content

Commit

Permalink
Pipelining
Browse files Browse the repository at this point in the history
This is a major rewrite, pipelining all modules in the SID chip in order to
save FPGA resources:

* Power-on initialization of oscillators, noise LFSRs, and envelope counters
* Register writes
* Update and synchronization of oscillators
* Waveform generation
* Envelope generation
* Voice DCA
* Filter / audio output
  • Loading branch information
daglem committed May 6, 2023
1 parent 951a3ad commit e97a79f
Show file tree
Hide file tree
Showing 23 changed files with 1,470 additions and 1,147 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/gateware-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ jobs:
- name: Install OSS CAD Suite
uses: YosysHQ/setup-oss-cad-suite@v2
with:
osscadsuite-version: '2023-04-04'
osscadsuite-version: '2023-05-05'

- name: Build gateware
run: |
DISTDIR="reDIP-SID-${GITHUB_REF_NAME}"
mkdir $DISTDIR
make -C gateware
cp -p gateware/README.md gateware/flash.bat gateware/flash.sh gateware/redip_sid.bin $DISTDIR
cp -p gateware/README.md gateware/Pipelining.md gateware/flash.bat gateware/flash.sh gateware/redip_sid.bin $DISTDIR
tar -czvf $DISTDIR.tar.gz $DISTDIR
zip -r $DISTDIR.zip $DISTDIR
Expand Down
11 changes: 8 additions & 3 deletions gateware/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ MOD = $(TOP).sv \
i2s_dsp_mode.sv \
muladd.sv \
sid_io.sv \
sid_pot.sv \
sid_control.sv \
sid_waveform.sv \
sid_envelope.sv \
sid_pot.sv \
sid_dac.sv \
sid_voice.sv \
sid_filter.sv \
sid_core.sv \
sid_api.sv

MUACM ?= 0
Expand All @@ -29,6 +29,11 @@ ifeq "$(SID2)" "1"
FLG += -DSID2
endif

RIPPLE_COUNTERS ?= 0
ifeq "$(RIPPLE_COUNTERS)" "1"
FLG += -DRIPPLE_COUNTERS
endif

SRC = $(PKG) $(FUN) $(MOD)

all: $(TOP).bin
Expand Down Expand Up @@ -63,7 +68,7 @@ prog: $(TOP).bin
dfu-util -d 1d50:6159,:6156 -a 0 -D $< -R

sim:
verilator --Mdir sim_trace --timescale "1ns / 1ns" --trace-fst --trace-structs --trace-underscore --clk clk --cc -O3 -CFLAGS "-Wall" --x-assign fast --x-initial fast --noassert --exe --build -Icells_sim sid_pkg.sv sid_api.sv --top sid_api sid_api_sim.cpp
verilator --Mdir sim_trace -DVM_TRACE -DRIPPLE_COUNTERS --timescale "1ns / 1ns" --trace-fst --trace-structs --trace-underscore --clk clk --cc -O3 -CFLAGS "-Wall" --x-assign fast --x-initial fast --noassert --exe --build -Icells_sim sid_pkg.sv sid_api.sv --top sid_api sid_api_sim.cpp
verilator --Mdir sim_audio --clk clk --cc -O3 -CFLAGS "-Wall" --x-assign fast --x-initial fast --noassert --exe --build -Icells_sim sid_pkg.sv sid_api.sv --top sid_api sid_api_sim.cpp

clean:
Expand Down
88 changes: 88 additions & 0 deletions gateware/Pipelining.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Pipelining

In order to make accurate SID emulation fit on a Lattice iCE40UP5K FPGA, it is
necessary to use pipelining and time-division multiplexing of resources.

Two synchronized pipelines are implemented; the voice pipeline and the filter
pipeline. The combined pipelines have a total of 14 pipeline stages. Two SID
chips are emulated, with audio outputs ready at cycle 15 and 20, respectively.

## Voice pipeline

The voice pipeline combines several SID modules, and produces one voice output
per cycle for two SID chips. The table below depicts the data path for SID 1,
voice 1.

| Stages | Module | Cycle 1 | Cycle 2 | Cycle 3 | Cycle 4 | Cycle 5 | Cycle 6 | Cycle 7 | Cycle 8 |
| ----: | -------------------| -------------- |--------------- | -------------- | --------------| ----------------| ----------------- | --------------------- | ----------- |
| 1 - 3 | Control registers | Write voice 1 | Write voice 2 | Write voice 3 | | | | | |
| 2 - 4 | Oscillator | | Update osc. 1 | Update osc. 2 | Sync osc. 1-3 | | | | |
| 2 - 7 | Waveform generator | | Buffer pulse 1 | Buffer pulse 2 | Update noise | Waveform select | Update waveform 0 | | |
| 7 - 8 | Envelope generator | | | | | | Update counters | | |
| 7 - 8 | Voice DCA | | | | | | Buffer wav/env | DCA = wav*env | |
| | | | | | | | | | Voice 1 out |

The voice pipeline starts on the falling edge of the ϕ₂ clock, brought into the
FPGA clock domain by a two-stage synchronizer.

As can be seen from the table above, output for voice 1 is ready on cycle
8. Outputs from the waveform and envelope generators are ready on cycle 6, so
data for the read-only registers OSC3 and ENV3 are also ready on cycle 8 (6 +
2).

The oscillator module adds a latency of two cycles; this is required for
synchronization of oscillators.

In order to meet MOS6510 bus timing, writes to the filter control registers for
SID 1 and 2 are included in the voice pipeline at cycle 1 and 4, respectively.
This is not shown in the table above.

## Filter pipeline

A separate pipeline is used to update filter state variables and produce audio
outputs for the two SID chips. The table below depicts the data path for the
audio output of SID 1.

| Stages | Description | Cycle 1 | Cycle 2 | Cycle 3 | Cycle 4 | Cycle 5 | Cycle 6 | Cycle 7 | Cycle 8 | Cycle 9 |
| -----: | ----------------------- | -------------- | ---------| ---------| ---------- | -------------------| --------------------- | ---------------- | ------------- | ----------- |
| 1 - 5 | Direct path mux / sum | vd = 0 | vd += v1 | vd += v2 | vd += v3 | vd += extin | (vd2 = 0, buffer vd1) | | | |
| 1 - 5 | Filter input mux / sum | vi = 0 | vi += v1 | vi += v2 | vi += v3 | vi += extin | (vi2 = 0) | | | |
| 2 - 4 | Computation of factors | | 1/Q, fc | w0 | w0 | | | | | |
| 4 - 8 | Multiply-add | | | | 0 - w0*vbp | 0 - w0*vhp | -(vlp + vi) + 1/Q*vbp | | vol*(vd + vf) | |
| 5 - 7 | Filter state update | | | | | vlp = vlp + muladd | vbp = vbp + muladd | vhp = muladd | | |
| 4 - 7 | Filter path mux / sum | | | | vf = 0 | vf += vlp | vf += vbp | vf += vhp | | |
| | | | | | | | | | | Audio 1 out |

The filter pipeline starts at voice pipeline cycle 7. At filter pipeline cycles
4 and 5, idle cycles are injected in the voice pipeline (at cycle 10). As can
be seen from the table above, filter pipeline cycles 5 and 6 are used to mux
and sum in SID 1 EXT IN, and to initialize voice accumulators for SID 2.

## Timing for register read / write

Read / write of SID registers is done while the ϕ₂ clock is high.

In the following we will assume that we have a minimum of 20 FPGA cycles
available between each falling edge of ϕ₂. At a 24MHz FPGA clock, this
corresponds to a 1.2MHz clock, or a 1.02MHz NTSC C64 clock with a cycle to
cycle (C2C) jitter peak value of 147ns, or 15%.

On writes, the MOS6510 holds address and data signals for a minimum of 10ns
after the falling edge of ϕ₂ (THA and THR in the datasheet). The reDIP SID
gateware takes advantage of this by using iCEGate™ latches to freeze the
signals for as long as ϕ₂ is low. According to the MOS6510 datasheet, at a 1MHz
clock, the maximum pulse width of ϕ₂ is 510ns. Assuming that the corresponding
minimum period between the falling and rising edge of ϕ₂ is then in the
ballpark of 490ns, we have 20*490/1000 = 9.8 FPGA cycles available for
writes. Accounting for two cycles spent to bring ϕ₂ into the FPGA clock domain,
we are still within margin as the last SID register is written at cycle 6 in
the voice pipeline.

For reads, the MOS6510 datasheet specifies a minimum Data Stability Time Period
(TDSU) of 100ns, i.e. data must be output on the data bus at least 100ns before
the falling edge of ϕ₂. SID 1 OSC3/ENV3 are ready on cycle 8 in the voice
pipeline, i.e. SID 2 OSC3/ENV3 are ready on cycle 8 + 3 = 11, just after the
voice pipeline is paused for two cycles. Accounting for another two cycles
spent to bring ϕ₂ into the FPGA clock domain, a cycle to register OSC3/ENV3,
and a cycle for registered pin outputs, we get a minimum TDSU of (20 - 11 - 2 -
2 - 1 - 1)/24Mhz = 125ns, which is within specification.
32 changes: 20 additions & 12 deletions gateware/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,44 @@

## Description

The reDIP SID FPGA gateware provides high quality SID emulation for
the [reDIP SID](https://github.com/daglem/reDIP-SID) hardware.
The reDIP SID FPGA gateware provides high quality SID emulation for the
[reDIP SID](https://github.com/daglem/reDIP-SID) hardware.

The gateware owes its existence to [reSID](https://github.com/daglem/reSID),
the [SID internals documentation](https://github.com/libsidplayfp/SID_schematics/wiki)
by Leandro "drfiemost" Nini and Dieter "ttlworks" Mueller, auxiliary code and
guidance from Sylvain "tnt" Munaut, and a lot of work :-)

The gateware implements cycle accurate emulation of the SID digital
logic, and quite a few SID analog peculiarities. In order to make
reasonably accurate emulation of some of these fit in the iCE40UP5K
FPGA, a few novelties have been invented:
The gateware implements cycle accurate emulation of the SID digital logic, and
quite a few SID analog peculiarities. In order to make reasonably accurate
emulation of some of these fit in the iCE40UP5K FPGA, a few novelties have been
invented:

* Combined sawtooth/triangle and pulse/sawtooth/triangle waveforms without lookup tables.
* MOS6581 waveform, envelope, and filter cutoff DAC emulation without lookup tables.
* Parameterizable filter cutoff curves requiring only a single 16kbit lookup table.

By default, a single MOS6581 chip is emulated. The gateware also
implements MOS8580 emulation and simultaneous emulation of two chips,
however runtime configuration of these features are not yet
implemented.
Furthermore, FPGA resources are shared by [pipelining](Pipelining.md) and
time-division multiplexing. This is in contrast to the real SID chip, where
there was sufficient die area to simply repeat functional modules for each
voice.

By default, a single MOS6581 chip is emulated. The gateware also implements
MOS8580 emulation and simultaneous emulation of two chips, however runtime
configuration of these features are not yet implemented.

## Installation

The gateware may be installed on the reDIP SID hardware via USB using
[dfu-util](https://dfu-util.sourceforge.net/):

* Connect a USB cable. The green LED should start blinking.
* Connect a USB cable to enter the bootloader. The green LED should start blinking.
* Install the gateware with `./flash.sh` (Linux / Mac OS) or `flash.bat` (Windows).
* Disconnect USB, and then either press the user button or power cycle the board.
* Disconnect USB. If the board is still powered, then press the user button to boot.

Depending on the previously installed gateware, in order to enter the
bootloader it may be required to keep the user button pressed while powering up
the board.

## License

Expand Down
2 changes: 2 additions & 0 deletions gateware/cells_sim/SB_IO.v
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
`define ICE40_DEFAULT_ASSIGNMENT_1
`endif

/* verilator lint_off UNUSED */
/* verilator lint_off COMBDLY */
/* verilator lint_off LATCH */
module SB_IO (
Expand Down Expand Up @@ -117,3 +118,4 @@ endspecify
endmodule
/* verilator lint_on LATCH */
/* verilator lint_on COMBDLY */
/* verilator lint_on UNUSED */
4 changes: 4 additions & 0 deletions gateware/cells_sim/SB_PLL40_2F_CORE.v
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* verilator lint_off UNUSED */
/* verilator lint_off UNDRIVEN */
(* blackbox *)
module SB_PLL40_2F_CORE (
input REFERENCECLK,
Expand Down Expand Up @@ -32,3 +34,5 @@ module SB_PLL40_2F_CORE (
parameter TEST_MODE = 1'b0;
parameter EXTERNAL_DIVIDE_FACTOR = 1;
endmodule
/* verilator lint_on UNDRIVEN */
/* verilator lint_on UNUSED */
4 changes: 4 additions & 0 deletions gateware/cells_sim/SB_RGBA_DRV.v
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* verilator lint_off UNUSED */
/* verilator lint_off UNDRIVEN */
(* blackbox *)
module SB_RGBA_DRV(
input CURREN,
Expand All @@ -14,3 +16,5 @@ parameter RGB0_CURRENT = "0b000000";
parameter RGB1_CURRENT = "0b000000";
parameter RGB2_CURRENT = "0b000000";
endmodule
/* verilator lint_on UNDRIVEN */
/* verilator lint_on UNUSED */
2 changes: 2 additions & 0 deletions gateware/cells_sim/SB_WARMBOOT.v
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/* verilator lint_off UNUSED */
(* blackbox, keep *)
module SB_WARMBOOT (
input BOOT,
input S1,
input S0
);
endmodule
/* verilator lint_on UNUSED */
2 changes: 2 additions & 0 deletions gateware/i2c_master.v
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@ module i2c_master #(
);

// Commands
/* verilator lint_off UNUSED */
localparam [1:0] CMD_START = 2'b00;
localparam [1:0] CMD_STOP = 2'b01;
localparam [1:0] CMD_WRITE = 2'b10;
localparam [1:0] CMD_READ = 2'b11;
/* verilator lint_on UNUSED */

// FSM states
localparam
Expand Down
7 changes: 2 additions & 5 deletions gateware/redip_sid.sv
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// ----------------------------------------------------------------------------
// This file is part of reDIP SID, a MOS 6581/8580 SID FPGA emulation platform.
// Copyright (C) 2022 Dag Lem <resid@nimrod.no>
// Copyright (C) 2022 - 2023 Dag Lem <resid@nimrod.no>
//
// This source describes Open Hardware and is licensed under the CERN-OHL-S v2.
//
Expand Down Expand Up @@ -74,7 +74,6 @@ module redip_sid (
);

// SID API parameters.
logic phi2_x;
sid::bus_i_t bus_i;
sid::cs_t cs;
sid::reg8_t data_o;
Expand Down Expand Up @@ -108,7 +107,7 @@ module redip_sid (
.scl_led (i2c_scl_led_n),
.sda_btn (i2c_sda_btn_n),
.btn (),
.led (~cs.cs_n & bus_i.we),
.led (~cs.cs_n & bus_i.phi2 & ~bus_i.r_w_n),
.done (),
.clk (clk_24),
.rst (rst_24)
Expand All @@ -126,7 +125,6 @@ module redip_sid (
.pad_phi2 (phi2),
.pad_res_n (res_n),
.pad_pot ({ pot_y, pot_x }),
.phi2 (phi2_x),
.bus_i (bus_i),
.cs (cs),
.data_o (data_o),
Expand All @@ -137,7 +135,6 @@ module redip_sid (
// SID API.
sid_api sid_api (
.clk (clk_24),
.phi2 (phi2_x),
.bus_i (bus_i),
.cs (cs),
.data_o (data_o),
Expand Down
2 changes: 2 additions & 0 deletions gateware/sgtl5000_init.v
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ module sgtl5000_init (
// ----------

// Instance
/* verilator lint_off PINCONNECTEMPTY */
i2c_master #(
.DW(4)
) master_I (
Expand All @@ -216,6 +217,7 @@ module sgtl5000_init (
.clk (clk),
.rst (rst)
);
/* verilator lint_on PINCONNECTEMPTY */

// Control
always @(*)
Expand Down
Loading

0 comments on commit e97a79f

Please sign in to comment.