From e97a79fb92fded47b049da30bf1fe695a597bfc5 Mon Sep 17 00:00:00 2001 From: Dag Lem Date: Sat, 6 May 2023 09:35:56 +0200 Subject: [PATCH] Pipelining 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 --- .github/workflows/gateware-release.yml | 4 +- gateware/Makefile | 11 +- gateware/Pipelining.md | 88 +++++ gateware/README.md | 32 +- gateware/cells_sim/SB_IO.v | 2 + gateware/cells_sim/SB_PLL40_2F_CORE.v | 4 + gateware/cells_sim/SB_RGBA_DRV.v | 4 + gateware/cells_sim/SB_WARMBOOT.v | 2 + gateware/i2c_master.v | 2 + gateware/redip_sid.sv | 7 +- gateware/sgtl5000_init.v | 2 + gateware/sid_api.sv | 381 +++++++++----------- gateware/sid_api_sim.cpp | 7 +- gateware/sid_control.sv | 224 ++++++++++++ gateware/sid_core.sv | 176 ---------- gateware/sid_dac.sv | 4 +- gateware/sid_envelope.sv | 458 +++++++++++++------------ gateware/sid_filter.sv | 297 ++++++++++------ gateware/sid_io.sv | 57 ++- gateware/sid_pkg.sv | 132 ++----- gateware/sid_pot.sv | 18 +- gateware/sid_voice.sv | 251 +++++--------- gateware/sid_waveform.sv | 454 ++++++++++++++++++------ 23 files changed, 1470 insertions(+), 1147 deletions(-) create mode 100644 gateware/Pipelining.md create mode 100644 gateware/sid_control.sv delete mode 100644 gateware/sid_core.sv diff --git a/.github/workflows/gateware-release.yml b/.github/workflows/gateware-release.yml index e551f84..c271174 100644 --- a/.github/workflows/gateware-release.yml +++ b/.github/workflows/gateware-release.yml @@ -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 diff --git a/gateware/Makefile b/gateware/Makefile index 5d5a365..d796e6e 100644 --- a/gateware/Makefile +++ b/gateware/Makefile @@ -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 @@ -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 @@ -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: diff --git a/gateware/Pipelining.md b/gateware/Pipelining.md new file mode 100644 index 0000000..ab3c276 --- /dev/null +++ b/gateware/Pipelining.md @@ -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. diff --git a/gateware/README.md b/gateware/README.md index 8ab9945..909ae1b 100644 --- a/gateware/README.md +++ b/gateware/README.md @@ -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 diff --git a/gateware/cells_sim/SB_IO.v b/gateware/cells_sim/SB_IO.v index 0b0517b..2b75719 100644 --- a/gateware/cells_sim/SB_IO.v +++ b/gateware/cells_sim/SB_IO.v @@ -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 ( @@ -117,3 +118,4 @@ endspecify endmodule /* verilator lint_on LATCH */ /* verilator lint_on COMBDLY */ +/* verilator lint_on UNUSED */ diff --git a/gateware/cells_sim/SB_PLL40_2F_CORE.v b/gateware/cells_sim/SB_PLL40_2F_CORE.v index 7ffa7ae..997af3e 100644 --- a/gateware/cells_sim/SB_PLL40_2F_CORE.v +++ b/gateware/cells_sim/SB_PLL40_2F_CORE.v @@ -1,3 +1,5 @@ +/* verilator lint_off UNUSED */ +/* verilator lint_off UNDRIVEN */ (* blackbox *) module SB_PLL40_2F_CORE ( input REFERENCECLK, @@ -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 */ diff --git a/gateware/cells_sim/SB_RGBA_DRV.v b/gateware/cells_sim/SB_RGBA_DRV.v index 5c30d37..38058bc 100644 --- a/gateware/cells_sim/SB_RGBA_DRV.v +++ b/gateware/cells_sim/SB_RGBA_DRV.v @@ -1,3 +1,5 @@ +/* verilator lint_off UNUSED */ +/* verilator lint_off UNDRIVEN */ (* blackbox *) module SB_RGBA_DRV( input CURREN, @@ -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 */ diff --git a/gateware/cells_sim/SB_WARMBOOT.v b/gateware/cells_sim/SB_WARMBOOT.v index 6d9b3e1..d7383e4 100644 --- a/gateware/cells_sim/SB_WARMBOOT.v +++ b/gateware/cells_sim/SB_WARMBOOT.v @@ -1,3 +1,4 @@ +/* verilator lint_off UNUSED */ (* blackbox, keep *) module SB_WARMBOOT ( input BOOT, @@ -5,3 +6,4 @@ module SB_WARMBOOT ( input S0 ); endmodule +/* verilator lint_on UNUSED */ diff --git a/gateware/i2c_master.v b/gateware/i2c_master.v index b4a1a29..70e84e6 100644 --- a/gateware/i2c_master.v +++ b/gateware/i2c_master.v @@ -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 diff --git a/gateware/redip_sid.sv b/gateware/redip_sid.sv index dec0426..da12286 100644 --- a/gateware/redip_sid.sv +++ b/gateware/redip_sid.sv @@ -1,6 +1,6 @@ // ---------------------------------------------------------------------------- // This file is part of reDIP SID, a MOS 6581/8580 SID FPGA emulation platform. -// Copyright (C) 2022 Dag Lem +// Copyright (C) 2022 - 2023 Dag Lem // // This source describes Open Hardware and is licensed under the CERN-OHL-S v2. // @@ -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; @@ -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) @@ -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), @@ -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), diff --git a/gateware/sgtl5000_init.v b/gateware/sgtl5000_init.v index ed5e46c..72bbfbf 100644 --- a/gateware/sgtl5000_init.v +++ b/gateware/sgtl5000_init.v @@ -200,6 +200,7 @@ module sgtl5000_init ( // ---------- // Instance + /* verilator lint_off PINCONNECTEMPTY */ i2c_master #( .DW(4) ) master_I ( @@ -216,6 +217,7 @@ module sgtl5000_init ( .clk (clk), .rst (rst) ); + /* verilator lint_on PINCONNECTEMPTY */ // Control always @(*) diff --git a/gateware/sid_api.sv b/gateware/sid_api.sv index d87f8cd..56f6aae 100644 --- a/gateware/sid_api.sv +++ b/gateware/sid_api.sv @@ -1,6 +1,6 @@ // ---------------------------------------------------------------------------- // This file is part of reDIP SID, a MOS 6581/8580 SID FPGA emulation platform. -// Copyright (C) 2022 Dag Lem +// Copyright (C) 2022 - 2023 Dag Lem // // This source describes Open Hardware and is licensed under the CERN-OHL-S v2. // @@ -16,19 +16,15 @@ `default_nettype none -module sid_api #( - // FC offset for average 6581 filter curve. - localparam FC_OFFSET_6581 = 12'sh600 -)( +module sid_api ( input logic clk, - input logic phi2, input sid::bus_i_t bus_i, input sid::cs_t cs, output sid::reg8_t data_o, input sid::pot_i_t pot_i, output sid::pot_o_t pot_o, input sid::audio_t audio_i, - output sid::audio_t audio_o + output sid::audio_t audio_o = '0 ); initial begin @@ -37,31 +33,52 @@ module sid_api #( end // SID core clock phase. - logic phi2_prev = 0; - (* onehot *) - sid::phase_t phase = 0; + logic phi2_prev = 0; + + // SID pipeline cycle counters. + sid::cycle_t voice_cycle_count = 0; + sid::cycle_t voice_cycle; + logic voice_cycle_idle = 0; + sid::cycle_t filter_cycle = 0; + + always_comb begin + // Idling of voice pipeline. + voice_cycle_idle = filter_cycle == 4 || filter_cycle == 5 || filter_cycle == 9 || filter_cycle == 10; + voice_cycle = voice_cycle_idle ? 0 : voice_cycle_count; + end always_ff @(posedge clk) begin - phi2_prev <= phi2; - phase <= { phase[1:0], phi2_prev & ~phi2 }; + // Start voice pipeline after the falling edge of phi2. + // Keep counting until the counter wraps around to zero. + // Pause voice pipeline at filter pipeline cycle 5 and 6, for the + // latter pipeline to catch up. + voice_cycle_count <= voice_cycle_count + 4'(phi2_prev & ~bus_i.phi2 || (voice_cycle_count != 0 && + !voice_cycle_idle)); + + // Start filter pipeline at voice pipeline cycle 7; one cycle before + // the first voice output is ready. + // Keep counting until the counter wraps around to zero. + filter_cycle <= filter_cycle + 4'(voice_cycle == 6 || filter_cycle != 0); + + phi2_prev <= bus_i.phi2; end // Tick approximately every ms, for smaller counters in submodules. // ~1MHz / 1024 = ~1kHz - logic [9:0] timer = 0; - logic [10:0] timer_next; - logic timer_tick; + logic [9:0] count_us = 0; + logic [10:0] count_us_next; + logic tick_ms; always_comb begin // Use carry as tick. - timer_next = { 1'b0, timer } + 1; - timer_tick = timer_next[10]; + count_us_next = { 1'b0, count_us } + 1; + tick_ms = count_us_next[10]; end always_ff @(posedge clk) begin - if (phase[sid::PHI2]) begin - // Update timer, discarding carry. - timer <= timer_next[9:0]; + if (voice_cycle == 1) begin + // Update counter, discarding carry. + count_us <= count_us_next[9:0]; end end @@ -75,211 +92,155 @@ module sid_api #( sid::cfg_t sid2_cfg = { sid::MOS6581, sid::D400, 9'd250, 11'sd0 }; `endif // NB! Don't put multi-bit variables in arrays, as Yosys handles that incorrectly. - sid::reg8_t sid1_data_o, sid2_data_o; logic [1:0] sid_cs; + logic [1:0] model; - // Digital outputs from SID cores. - sid::core_o_t core1_o, core2_o; - sid::reg8_t sid1_osc3, sid2_osc3; + always_comb begin + model = { sid2_cfg.model, sid1_cfg.model }; + end - // SID core #1. - sid_core sid1 ( - .clk (clk), - .tick_ms (timer_tick), - .model (sid1_cfg.model), - .bus_i (bus_i), - .phase (phase), - .cs (sid_cs[0]), - .data_o (sid1_data_o), - .pot_i (pot_i), - .pot_o (pot_o), - .out (core1_o), - .osc3 (sid1_osc3) - ); + // SID read-only registers. + sid::pot_reg_t sid1_pot; + sid::reg8_t sid1_osc3 = 0, sid2_osc3 = 0; + sid::reg8_t sid1_env3 = 0, sid2_env3 = 0; - // SID core #2 - no POT pins. - /* verilator lint_off PINCONNECTEMPTY */ - sid_core sid2 ( - .clk (clk), - .tick_ms (timer_tick), - .model (sid2_cfg.model), - .bus_i (bus_i), - .phase (phase), - .cs (sid_cs[1]), - .data_o (sid2_data_o), - .pot_i (2'b00), - .pot_o (), - .out (core2_o), - .osc3 (sid2_osc3) - ); - /* verilator lint_on PINCONNECTEMPTY */ + always_comb begin + // Chip select decode. + // SID 2 address is configurable. + // SID 1 is always located at D400. + sid_cs[1] = sid2_cfg.addr == sid::D400 ? ~cs.cs_n : + sid2_cfg.addr[sid::D420_BIT] & ~cs.cs_n & cs.a5 | + sid2_cfg.addr[sid::D500_BIT] & ~cs.cs_n & cs.a8 | + sid2_cfg.addr[sid::DE00_BIT] & ~cs.cs_io1_n; + sid_cs[0] = sid2_cfg.addr == sid::D400 ? ~cs.cs_n : + ~cs.cs_n & ~sid_cs[1]; - // Pipeline for voice outputs. - sid::model_e voice_model; - sid::voice_i_t voice_i; - sid::reg8_t osc_o; - sid::s22_t voice_o; + // Default to SID 1. + // Return FF for SID 2 POTX/Y. + mreg.pot = (sid_cs == 2'b10) ? '1 : sid1_pot; + mreg.osc3 = (sid_cs == 2'b10) ? sid2_osc3 : sid1_osc3; + mreg.env3 = (sid_cs == 2'b10) ? sid2_env3 : sid1_env3; + end - logic [3:0] voice_stage = 0, next_voice_stage; + // SID control registers. + sid::freq_pw_t freq_pw_1; + logic [2:0] test; + logic [2:0] sync; + sid::control_t control_3, control_4, control_5; + sid::envelope_reg_t ereg_5; + sid::filter_reg_t freg_1; + sid::misc_reg_t mreg; + + sid_control control ( + .clk (clk), + .tick_ms (tick_ms), + .voice_cycle (voice_cycle), + .filter_cycle (filter_cycle), + .bus_i (bus_i), + .cs (sid_cs), + .model (model), + .freq_pw_1 (freq_pw_1), + .test (test), + .sync (sync), + .control_3 (control_3), + .control_4 (control_4), + .control_5 (control_5), + .ereg_5 (ereg_5), + .freg_1 (freg_1), + .mreg (mreg), + .data_o (data_o) + ); - sid_voice voice_pipeline ( - .clk (clk), - .tick_ms (timer_tick), - .active (voice_stage >= 2 && voice_stage <= 8), - .model (voice_model), - .voice_i (voice_i), - .voice_o (voice_o), // 1 cycle delay - .osc_o (osc_o) // 1 cycle delay + // SID waveform generator. + sid::reg12_t wav; + + sid_waveform waveform ( + .clk (clk), + .tick_ms (tick_ms), + .cycle (voice_cycle), + .res (bus_i.res), + .model (model), + .freq_pw_1 (freq_pw_1), + .test (test), + .sync (sync), + .control_3 (control_3), + .control_4 (control_4), + .control_5 (control_5), + .wav (wav) ); - // Pipeline for filter outputs. - sid::filter_i_t filter_i; - sid::s20_t filter_o; - sid::s20_t filter_o_left; - sid::s22_t audio_i_right; - - logic [3:0] filter_state = 0, next_filter_state; - /* verilator lint_off UNUSED */ - logic filter_no; - /* verilator lint_on UNUSED */ - logic [2:0] filter_stage; - logic [1:0] filter_done = 0, next_filter_done; - - sid_filter filter_pipeline ( - .clk (clk), - .stage (filter_stage), - .filter_i (filter_i), - .audio_o (filter_o) // 8 cycle delay + // SID envelope generator. + sid::reg8_t env; + + sid_envelope envelope ( + .clk (clk), + .cycle (voice_cycle), + .res (bus_i.res), + .ereg_5 (ereg_5), + .env (env) ); - always_comb begin - case (voice_stage) - // Start voice pipeline when the SIDs are done. - 0: next_voice_stage = { 3'b0, phase[sid::PHI1] }; - // Finished after stage 9. - 9: next_voice_stage = 0; - default: - next_voice_stage = voice_stage + 1; - endcase - - case (filter_state) - // Start filter pipeline when all voices from SID #1 are done. - 0: next_filter_state = { 3'b0, voice_stage == 5 }; - // Start filter #2 after filter #1 is done. - // filter_stage will wrap around to zero after filter #2 is done. - 7: next_filter_state = { 1'b1, 3'd1 }; - default: - next_filter_state = filter_state + 1; - endcase - - filter_no = filter_state[3]; - filter_stage = filter_state[2:0]; - - case (filter_state) - { 1'b0, 3'd7 }: - next_filter_done = 1; - { 1'b1, 3'd7 }: - next_filter_done = 2; - default: - next_filter_done = 0; - endcase + // Store OSC3 and ENV3 for both SIDs. + always_ff @(posedge clk) begin + if (voice_cycle == 8) begin + sid1_osc3 <= wav[11-:8]; + sid1_env3 <= env; + end + + if (voice_cycle == 11) begin + sid2_osc3 <= wav[11-:8]; + sid2_env3 <= env; + end end + // Pipeline for voice outputs. + sid::s22_t dca; + + sid_voice voice ( + .clk (clk), + .cycle (voice_cycle), + .model (model), + .wav (wav), + .env (env), + .dca (dca) + ); + + // Pipeline for filter outputs. + sid::s20_t filter_o; + sid::s24_t ext_in_1 = '0; + sid::s24_t ext_in_2 = '0; + sid::s24_t audio_o1 = '0; + always_ff @(posedge clk) begin - // Calculate 2*3 voice outputs. - // osc_o and voice_o are delayed by 1 cycle. - // FIXME: Calculate voice3 first, in order to have OSC3 ready 2 cycles earlier? - case (voice_stage) - 1: begin - voice_model <= sid1_cfg.model; - voice_i <= core1_o.voice1; - end - 2: begin - voice_i <= core1_o.voice2; - end - 3: begin - voice_i <= core1_o.voice3; - - filter_i.voice1 <= voice_o; - end - 4: begin - voice_model <= sid2_cfg.model; - voice_i <= core2_o.voice1; - - filter_i.voice2 <= voice_o; - end - 5: begin - voice_i <= core2_o.voice2; - - filter_i.voice3 <= voice_o; - sid1_osc3 <= osc_o; - - // Setup for SID #1 filter pipeline. - filter_i.model <= sid1_cfg.model; - filter_i.fc_base <= sid1_cfg.fc_base; - filter_i.fc_offset <= sid1_cfg.fc_offset + FC_OFFSET_6581; - filter_i.regs <= core1_o.filter_regs; - filter_i.ext_in <= audio_i.left[23 -: 22]; - // Save audio input for SID #2. - audio_i_right <= audio_i.right[23 -: 22]; - - // Ready for SID #1 filter pipeline, see below. - end - 6: begin - voice_i <= core2_o.voice3; - - filter_i.voice1 <= voice_o; - end - 7: begin - filter_i.voice2 <= voice_o; - end - 8: begin - filter_i.voice3 <= voice_o; - sid2_osc3 <= osc_o; - end - 9: begin - // Setup for SID #2 filter pipeline. - // The filter input state is only used during the first 4 cycles - // in sid_filter, so it's safe to change it just now. - filter_i.model <= sid2_cfg.model; - filter_i.fc_base <= sid2_cfg.fc_base; - filter_i.fc_offset <= sid2_cfg.fc_offset + FC_OFFSET_6581; - filter_i.regs <= core2_o.filter_regs; - filter_i.ext_in <= audio_i_right; - end - endcase - - voice_stage <= next_voice_stage; - - // Combine 2 audio stage outputs. - case (filter_done) - 1: begin - filter_o_left <= filter_o; - end - 2: begin - audio_o.left <= { filter_o_left, 4'b0 }; - audio_o.right <= { filter_o, 4'b0 }; - end - endcase - - filter_state <= next_filter_state; - filter_done <= next_filter_done; - end + if (filter_cycle == 1) begin + { ext_in_1, ext_in_2 } <= audio_i; + end else if (filter_cycle == 6) begin + ext_in_1 <= ext_in_2; + end - // Chip select decode. - always_comb begin - // SID #1 is always located at D400. - // SID #2 address is configurable. - sid_cs[1] = sid2_cfg.addr == sid::D400 ? ~cs.cs_n : - sid2_cfg.addr[sid::D420_BIT] & ~cs.cs_n & cs.a5 | - sid2_cfg.addr[sid::D500_BIT] & ~cs.cs_n & cs.a8 | - sid2_cfg.addr[sid::DE00_BIT] & ~cs.cs_io1_n; - sid_cs[0] = sid2_cfg.addr == sid::D400 ? ~cs.cs_n : - ~cs.cs_n & ~sid_cs[1]; + if (filter_cycle == 9) begin + audio_o1 <= { filter_o, 4'b0 }; + end else if (filter_cycle == 14) begin + audio_o <= { audio_o1, filter_o, 4'b0 }; + end end - always_comb begin - // Default to SID #1 for data out. - data_o = sid_cs == 2'b10 ? sid2_data_o : sid1_data_o; - end + sid_filter filter ( + .clk (clk), + .cycle (filter_cycle), + .freg (freg_1), + .cfg (filter_cycle <= 5 ? sid1_cfg : sid2_cfg), + // EXT IN or internal voice. + .voice_i (filter_cycle == 5 || filter_cycle == 10 ? ext_in_1[23-:22] : dca), + .audio_o (filter_o) + ); + + // SID POTX / POTY. + sid_pot potxy ( + .clk (clk), + .cycle (voice_cycle), + .pot_i (pot_i), + .pot_o (pot_o), + .pot (sid1_pot) + ); endmodule diff --git a/gateware/sid_api_sim.cpp b/gateware/sid_api_sim.cpp index db5984e..d521006 100644 --- a/gateware/sid_api_sim.cpp +++ b/gateware/sid_api_sim.cpp @@ -193,17 +193,17 @@ static void clk12(Vsid_api* api) { } static void phi2(Vsid_api* api) { - api->phi2 = 1; + api->bus_i |= (0b1 << 2); clk12(api); } static void phi1(Vsid_api* api) { - api->phi2 = 0; + api->bus_i &= ~(0b1 << 2); clk12(api); } static void write(Vsid_api* api, int addr, int data) { - api->bus_i = (addr << 11) | (data << 3) | (0b1 << 2) | (api->bus_i & 1); + api->bus_i = (addr << 11) | (data << 3) | (api->bus_i & 0b101); } @@ -243,7 +243,6 @@ Write waveform dump to "sid_api.fst". auto api = new Vsid_api; api->clk = 0; - api->phi2 = 0; api->bus_i = 0; api->cs = 0b0100; // cs_n = 0, cs_io1_n = 1 api->pot_i = 0; diff --git a/gateware/sid_control.sv b/gateware/sid_control.sv new file mode 100644 index 0000000..b62b680 --- /dev/null +++ b/gateware/sid_control.sv @@ -0,0 +1,224 @@ +// ---------------------------------------------------------------------------- +// This file is part of reDIP SID, a MOS 6581/8580 SID FPGA emulation platform. +// Copyright (C) 2023 Dag Lem +// +// This source describes Open Hardware and is licensed under the CERN-OHL-S v2. +// +// You may redistribute and modify this source and make products using it under +// the terms of the CERN-OHL-S v2 (https://ohwr.org/cern_ohl_s_v2.txt). +// +// This source is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY, +// INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A +// PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable conditions. +// +// Source location: https://github.com/daglem/reDIP-SID +// ---------------------------------------------------------------------------- + +`default_nettype none + +// SID control registers for two chips. +// Fully pipelined operation, shifting out registers for one voice per cycle. +module sid_control #( + localparam DBUS_VALUE_TTL_MOS6581 = 10'd7, + localparam DBUS_VALUE_TTL_MOS8580 = 10'd664 +)( + input logic clk, + input logic tick_ms, + input sid::cycle_t voice_cycle, + input sid::cycle_t filter_cycle, + input sid::bus_i_t bus_i, + input logic [1:0] cs, + input logic [1:0] model, + // Write-only registers. + output sid::freq_pw_t freq_pw_1, + output logic [2:0] test, + output logic [2:0] sync, + output sid::control_t control_3, + output sid::control_t control_4, + output sid::control_t control_5, + output sid::envelope_reg_t ereg_5, + output sid::filter_reg_t freg_1, + // Read-only registers. + input sid::misc_reg_t mreg, + output sid::reg8_t data_o +); + + /* verilator lint_off LITENDIAN */ + typedef union packed { + logic [0:6][7:0] bytes; + sid::voice_reg_t regs; + } voice_ctrl_t; + + typedef union packed { + logic [0:3][7:0] bytes; + sid::filter_reg_t regs; + } filter_ctrl_t; + + typedef union packed { + logic [0:3][7:0] bytes; + sid::misc_reg_t regs; + } misc_ctrl_t; + /* verilator lint_on LITENDIAN */ + + // Register sets for two SID chips. + // Handling of nowrshmsk for structs was added to Yosys to save 10% of the + // LCs in this design. + (* nowrshmsk *) + voice_ctrl_t v5 = '0, v4 = '0, v3 = '0, v2 = '0, v1 = '0, v0 = '0; + (* nowrshmsk *) + filter_ctrl_t f1 = '0, f0 = '0; + misc_ctrl_t m; + + // Value on data bus. + // FIXME: Yosys doesn't support multidimensional packed arrays outside + // of structs, nor arrays of structs. + typedef struct packed { + logic [1:0][7:0] value; + logic [1:0][9:0] age; + } dbus_t; + + (* nowrshmsk *) + dbus_t dbus = '0; + + // Register offset. + logic signed [4:0] vaddr_offset = 0; + + always_ff @(posedge clk) begin + if (voice_cycle == 0 || voice_cycle == 3) begin + vaddr_offset <= 0; + end else if (voice_cycle >= 1 && voice_cycle <= 2 || + voice_cycle >= 4 && voice_cycle <= 5) + begin + // Subtract a constant here so we can add instead of subtract the + // variable later, saving LCs which would otherwise be used for + // two's complement inversion. + vaddr_offset <= vaddr_offset - 7; + end + end + + // Register read / write. + logic cycle_cs; + logic signed [5:0] vaddr; + logic signed [5:0] faddr; + logic signed [5:0] maddr; + logic r; + logic w; + sid::reg8_t r_value; + + always_comb begin + // Assign to / from ports. + freq_pw_1 = v0.regs.waveform.freq_pw; + control_3 = v2.regs.waveform.control; + control_4 = v3.regs.waveform.control; + control_5 = v4.regs.waveform.control; + ereg_5 = v4.regs.envelope; + test = { v2.regs.waveform.control.test, v1.regs.waveform.control.test, v0.regs.waveform.control.test }; + sync = { v2.regs.waveform.control.sync, v1.regs.waveform.control.sync, v0.regs.waveform.control.sync }; + freg_1 = f1.regs; // Rotated before first use + m.regs = mreg; + + // cycle bit 2 is set for voice 4 - 6. + cycle_cs = cs[voice_cycle[2]]; + // cycle_cs = cs[voice_cycle >= 4]; + + // Address offsets. + vaddr = signed'(6'(bus_i.addr)) + 6'(vaddr_offset); + faddr = signed'(6'(bus_i.addr)) - 'sh15; + maddr = signed'(6'(bus_i.addr)) - 'sh19; + + // Read / write. + r = bus_i.phi2 & bus_i.r_w_n && maddr >= 0 && maddr <= 3; + w = ~bus_i.phi2 & ~bus_i.r_w_n && voice_cycle >= 1 && voice_cycle <= 6; + + // FIXME: Verilator complains about m.bytes[maddr[1:0]]. + r_value = m.bytes[maddr[2:0]]; + + // Data output, default to SID 1 (for both SIDs at D400). + // Read data could have been always output from dbus.value, however + // this would delay the output by one cycle, possibly violating the + // minimum MOS6510 TDSU of 100ns for SID 2 OSC3/ENV3. + // data_o = dbus.value[cs == 2'b10]; + data_o = cs && r ? r_value : dbus.value[cs == 2'b10]; + end + + // Value on data bus for each SID chip. + // FIXME: Attempting to put the for loop inside always_ff makes Verilator fail with + // %Error: Internal Error: sid_control.sv:147:55: ../V3LinkDot.cpp:2298: Bad package link + // always_ff @(posedge clk) begin + // for (int i = 0; i < 2; i++) begin : sid + for (genvar i = 0; i < 2; i++) begin : sid + always_ff @(posedge clk) begin + if (bus_i.res) begin + dbus.value[i] <= '0; + dbus.age[i] <= '0; + end else if (cs[i] && (r || w)) begin + // Keep last register read/write on data bus. + dbus.value[i] <= r ? r_value : bus_i.data; + dbus.age[i] <= 0; + end else if (dbus.age[i] == ((model[i] == sid::MOS6581) ? + DBUS_VALUE_TTL_MOS6581 : + DBUS_VALUE_TTL_MOS8580)) + begin + // Bus value has faded out. + dbus.value[i] <= 0; + end else if (voice_cycle == 1) begin + // Count up to complete fade-out of bus value. + dbus.age[i] <= dbus.age[i] + 10'(tick_ms); + end + end + end + + // Write-only registers. + always_ff @(posedge clk) begin + // Rotation on cycles 1 - 6 for writing. + // Rotation on six additional cycles to keep in sync with + // sid_waveform.sv and sid_envelope.sv + if (voice_cycle >= 1 && voice_cycle <= 12) begin + { v5, v4, v3, v2, v1, v0 } <= { v4, v3, v2, v1, v0, v5 }; + + if (bus_i.res) begin + v0.bytes <= '0; + end else if (cycle_cs && w && vaddr >= 0 && vaddr <= 6) begin + // Voice register write. + v0.bytes[vaddr[2:0]] <= bus_i.data; + end + end + + if (voice_cycle == 1 || voice_cycle == 4 || + filter_cycle == 5 || filter_cycle == 10) + begin + { f1, f0 } <= { f0, f1 }; + + if (bus_i.res) begin + f0.bytes <= '0; + end else if (cycle_cs && w && faddr >= 0 && faddr <= 3) begin + // Filter register write. + // FIXME: Verilator complains about f.bytes[faddr[1:0]]. + f0.bytes[faddr[2:0]] <= bus_i.data; + end + end + end + +`ifdef VM_TRACE + // Latch voices for simulation. + /* verilator lint_off UNUSED */ + voice_ctrl_t sim_voice[6]; + + always_ff @(posedge clk) begin + if (voice_cycle >= 2 && voice_cycle <= 7) begin + sim_voice[voice_cycle - 2] <= v0; + end + end + + filter_ctrl_t sim_filter[2]; + + always_ff @(posedge clk) begin + if (voice_cycle == 2) begin + sim_filter[0] <= f0; + end else if (voice_cycle == 5) begin + sim_filter[1] <= f0; + end + end + /* verilator lint_on UNUSED */ +`endif +endmodule diff --git a/gateware/sid_core.sv b/gateware/sid_core.sv deleted file mode 100644 index 69a884f..0000000 --- a/gateware/sid_core.sv +++ /dev/null @@ -1,176 +0,0 @@ -// ---------------------------------------------------------------------------- -// This file is part of reDIP SID, a MOS 6581/8580 SID FPGA emulation platform. -// Copyright (C) 2022 Dag Lem -// -// This source describes Open Hardware and is licensed under the CERN-OHL-S v2. -// -// You may redistribute and modify this source and make products using it under -// the terms of the CERN-OHL-S v2 (https://ohwr.org/cern_ohl_s_v2.txt). -// -// This source is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY, -// INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A -// PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable conditions. -// -// Source location: https://github.com/daglem/reDIP-SID -// ---------------------------------------------------------------------------- - -`default_nettype none - -module sid_core #( - localparam BUS_VALUE_TTL_MOS6581 = 10'd7, - localparam BUS_VALUE_TTL_MOS8580 = 10'd664 -)( - input logic clk, - input logic tick_ms, - input sid::model_e model, - input sid::bus_i_t bus_i, - input sid::phase_t phase, - input logic cs, - output sid::reg8_t data_o = 0, - input sid::pot_i_t pot_i, - output sid::pot_o_t pot_o, - output sid::core_o_t out, - input sid::reg8_t osc3 -); - - // Write-only / read-only registers. - sid::reg_i_t reg_i = '0; - sid::reg_o_t reg_o; - - always_comb begin - out.filter_regs = reg_i.regs.filter; - reg_o.regs.osc3 = osc3; - reg_o.regs.env3 = out.voice3.envelope; - end - - // SID waveform generators. - - // We could have generated the waveform generators, however Yosys currently - // doesn't support multidimensional packed arrays outside of structs. - /* verilator lint_off UNOPTFLAT */ - sid::sync_t sync1, sync2, sync3; - /* verilator lint_on UNOPTFLAT */ - - sid_waveform waveform1 ( - .clk (clk), - .tick_ms (tick_ms), - .res (bus_i.res), - .model (model), - .phase (phase), - .reg_i (reg_i.regs.voice1.waveform), - .sync_i (sync1), - .sync_o (sync2), - .out (out.voice1.waveform) - ); - - sid_waveform waveform2 ( - .clk (clk), - .tick_ms (tick_ms), - .res (bus_i.res), - .model (model), - .phase (phase), - .reg_i (reg_i.regs.voice2.waveform), - .sync_i (sync2), - .sync_o (sync3), - .out (out.voice2.waveform) - ); - - sid_waveform waveform3 ( - .clk (clk), - .tick_ms (tick_ms), - .res (bus_i.res), - .model (model), - .phase (phase), - .reg_i (reg_i.regs.voice3.waveform), - .sync_i (sync3), - .sync_o (sync1), - .out (out.voice3.waveform) - ); - - // SID envelope generators. - - sid_envelope envelope1 ( - .clk (clk), - .res (bus_i.res), - .phase (phase), - .reg_i (reg_i.regs.voice1.envelope), - .out (out.voice1.envelope) - ); - - sid_envelope envelope2 ( - .clk (clk), - .res (bus_i.res), - .phase (phase), - .reg_i (reg_i.regs.voice2.envelope), - .out (out.voice2.envelope) - ); - - sid_envelope envelope3 ( - .clk (clk), - .res (bus_i.res), - .phase (phase), - .reg_i (reg_i.regs.voice3.envelope), - .out (out.voice3.envelope) - ); - - // SID POTX / POTY. - sid_pot pot ( - .clk (clk), - .phase (phase), - .pot_reg (reg_o.regs.pot), - .pot_i (pot_i), - .pot_o (pot_o) - ); - - // Register read / write. - logic r; - logic w; - - // Fade-out of value on data bus. - logic [9:0] bus_ttl; - logic [9:0] bus_age = 0; - sid::reg8_t bus_value = 0; - - always_comb begin - // Read / write. - r = cs && bus_i.oe && bus_i.addr >= 'h19 && bus_i.addr < 'h1D; - w = cs && bus_i.we; - - // Time to live for value on data bus. - bus_ttl = (model == sid::MOS6581) ? BUS_VALUE_TTL_MOS6581 : BUS_VALUE_TTL_MOS8580; - end - - always_ff @(posedge clk) begin - // Output from register or bus value. - data_o <= r ? reg_o.bytes[bus_i.addr - 'h19] : bus_value; - - if (phase[sid::PHI2]) begin - // Note that write-only registers must be updated at PHI2, before PHI2_PHI1. - if (bus_i.res) begin - // Reset write-only registers. - reg_i <= '0; - end else if (w) begin - // Register write. - reg_i.bytes[bus_i.addr] <= bus_i.data; - end - - if (r) begin - // Keep last register read on data bus. - bus_value <= data_o; - bus_age <= 0; - end else if (w) begin - // Keep last register write on data bus. - bus_value <= bus_i.data; - bus_age <= 0; - end else begin - if (bus_i.res || bus_age == bus_ttl) begin - // Bus value has faded out. - bus_value <= 0; - end else begin - // Count up to complete fade-out of bus value. - bus_age <= bus_age + { 9'b0, tick_ms }; - end - end - end - end -endmodule diff --git a/gateware/sid_dac.sv b/gateware/sid_dac.sv index 6a8d779..adb69ff 100644 --- a/gateware/sid_dac.sv +++ b/gateware/sid_dac.sv @@ -1,6 +1,6 @@ // ---------------------------------------------------------------------------- // This file is part of reDIP SID, a MOS 6581/8580 SID FPGA emulation platform. -// Copyright (C) 2022 Dag Lem +// Copyright (C) 2022 - 2023 Dag Lem // // This source describes Open Hardware and is licensed under the CERN-OHL-S v2. // @@ -73,7 +73,7 @@ module sid_dac #( // Sum values for all set bits, adding 0.5 for rounding by truncation. always_comb begin bitsum = 1 << (SCALEBITS - 1); - for (integer i = 0; i < BITS; i++) begin + for (int i = 0; i < BITS; i++) begin bitsum += (vin[i] ? bitval[i] : 0); end vout = bitsum[MSB-:BITS]; diff --git a/gateware/sid_envelope.sv b/gateware/sid_envelope.sv index 4a0b40b..b30bf4f 100644 --- a/gateware/sid_envelope.sv +++ b/gateware/sid_envelope.sv @@ -1,6 +1,6 @@ // ---------------------------------------------------------------------------- // This file is part of reDIP SID, a MOS 6581/8580 SID FPGA emulation platform. -// Copyright (C) 2022 Dag Lem +// Copyright (C) 2022 - 2023 Dag Lem // // This source describes Open Hardware and is licensed under the CERN-OHL-S v2. // @@ -18,267 +18,303 @@ module sid_envelope #( // Default to no init, since the counter will reach 0 after a maximum of - // one exponential decay cycle (at decay = 0) after reset release. + // one exponential release cycle (at release = 0) after reset release. localparam INIT_ENV = 0, - localparam LFSR_COUNTERS = 1 + localparam ENV_INIT = INIT_ENV ? { 4{2'b10} } : 'hFF )( input logic clk, + input sid::cycle_t cycle, input logic res, - input sid::phase_t phase, - input sid::envelope_reg_t reg_i, - output sid::reg8_t out + input sid::envelope_reg_t ereg_5, + output sid::reg8_t env ); + // Initialization flag. + logic primed = 0; + // 8-bit envelope counter. // Odd bits are high on powerup. There is no immediate reset, however // env_cnt_en, rate_cnt_res, and exp_cnt_res are all set by reset. // This implies that, on the face of it, the counter will start out at - // ~AA = 55, and will then count down in the decay state while in reset, + // ~AA = 55, and will then count down in the release state while in reset, // wrapping around to FF each time 00 is reached. After reset release, the // counter will continue counting down until it finally reaches zero. // Note that the counter itself is FF at env=00, due to inversion in decay. - sid::reg8_t env_cnt = INIT_ENV ? { 4{2'b10} } : 'hFF; - sid::reg8_t env = 0; - logic env_cnt_en = 0; - logic env_cnt_inv = 0; - logic prev_gate = 0; + typedef struct packed { + sid::reg8_t env_cnt; + logic prev_FF; + logic prev_00; + logic prev_gate; + logic attack; + logic sustain; + sid::reg4_t adr; + logic env_cnt_en; + logic env_cnt_up; + logic prev_env_cnt_up; + logic env_cnt_inv; + // 15-bit rate counter. + sid::reg15_t rate_cnt; + logic prev_rate_cnt_res; + // 5-bit exponential decay counter. + sid::reg5_t exp_seg; + sid::reg5_t exp_cnt; + } envelope_t; + + envelope_t e5 = '0, e4 = '0, e3 = '0, e2 = '0, e1 = '0, e0 = '0; + logic rise_gate; - logic attack = 0; - logic env_cnt_up = 0; - logic sustain_cmp = 0; - logic sustain; - // 15-bit rate counter. - sid::reg15_t rate_cnt = LFSR_COUNTERS ? '1 : '0; - logic rate_cnt_res = 0; - logic prev_rate_cnt_res = 0; - sid::reg7_t exp_step = 0; - logic prev_FF = 0; - logic prev_00 = 0; - logic rise_FF = 0; - logic rise_00 = 0; - sid::reg5_t exp_seg = 0; - // 5-bit exponential decay counter. - sid::reg5_t exp_cnt = LFSR_COUNTERS ? '1 : '0; - logic exp_cnt_res = 0; - sid::reg4_t adr = 0; + logic rise_FF; + logic rise_00; + sid::reg7_t exp_step; + logic rate_cnt_res; + logic exp_cnt_res; always_comb begin - rise_gate = ~prev_gate & reg_i.gate; - sustain = sustain_cmp & reg_i.gate & ~attack; + // The counter output bits are inverted when the counter counts down. + // In the real SID, the final result is latched by phi2. To save + // storage, we instead invert the previous counter bits on the fly. + // FIXME: The XOR with primed only works for INIT_ENV = 0. + env = e5.env_cnt ^ { 8{~e5.prev_env_cnt_up ^ ~primed} }; - // Envelope output, to envelope DAC. - out = env; - end + // Exponential decay curve segment steps. + exp_step = { + env == 8'h00, + env == 8'h06, + env == 8'h0E, + env == 8'h1A, + env == 8'h36, + env == 8'h5D, + env == 8'hFF + }; - // Exponential decay curve segment steps. - always_ff @(posedge clk) begin - if (phase[sid::PHI2]) begin - // These signals are delayed by one cycle (latched by phi1); - // store values before update of exp_step. - rise_FF <= ~prev_FF & exp_step[0]; - rise_00 <= ~prev_00 & exp_step[6]; - prev_FF <= exp_step[0]; - prev_00 <= exp_step[6]; - - exp_step <= { - env == 8'h00, - env == 8'h06, - env == 8'h0E, - env == 8'h1A, - env == 8'h36, - env == 8'h5D, - env == 8'hFF - }; - end - end + rise_gate = ~e5.prev_gate & ereg_5.gate; + rise_FF = ~e5.prev_FF & exp_step[0]; + rise_00 = ~e5.prev_00 & exp_step[6]; - // Set/reset selectors for exponential curve segments. - for (genvar i = 0; i < $bits(exp_seg); i++) begin : seg - always_ff @(posedge clk) begin - if (phase[sid::PHI2_PHI1]) begin - if (exp_step[i] | exp_step[i + 2] | res) begin - exp_seg[i] <= 0; - end else if (exp_step[i + 1]) begin - exp_seg[i] <= 1; - end - end - end +`ifndef RIPPLE_COUNTERS + // Envelope rate counter reset / count. + rate_cnt_res = + (e5.adr == 'h0 && e5.rate_cnt == 'h7f00) || + (e5.adr == 'h1 && e5.rate_cnt == 'h0006) || + (e5.adr == 'h2 && e5.rate_cnt == 'h003c) || + (e5.adr == 'h3 && e5.rate_cnt == 'h0330) || + (e5.adr == 'h4 && e5.rate_cnt == 'h20c0) || + (e5.adr == 'h5 && e5.rate_cnt == 'h6755) || + (e5.adr == 'h6 && e5.rate_cnt == 'h3800) || + (e5.adr == 'h7 && e5.rate_cnt == 'h500e) || + (e5.adr == 'h8 && e5.rate_cnt == 'h1212) || + (e5.adr == 'h9 && e5.rate_cnt == 'h0222) || + (e5.adr == 'hA && e5.rate_cnt == 'h1848) || + (e5.adr == 'hB && e5.rate_cnt == 'h59b8) || + (e5.adr == 'hC && e5.rate_cnt == 'h3840) || + (e5.adr == 'hD && e5.rate_cnt == 'h77e2) || + (e5.adr == 'hE && e5.rate_cnt == 'h7625) || + (e5.adr == 'hF && e5.rate_cnt == 'h0a93) || + res; + + // Exponential counter reset / count. + exp_cnt_res = + // Exponential decay. + (e5.exp_seg[0] && e5.exp_cnt == 'h1c) || + (e5.exp_seg[1] && e5.exp_cnt == 'h11) || + (e5.exp_seg[2] && e5.exp_cnt == 'h1b) || + (e5.exp_seg[3] && e5.exp_cnt == 'h08) || + (e5.exp_seg[4] && e5.exp_cnt == 'h0f) || + // No exponential decay. + // Note that in this FPGA cycle we cannot use + // e5.attack, which is precalculated for the next + // SID cycle. e5.env_cnt_up = attack for this cycle. + (e5.prev_rate_cnt_res && (e5.env_cnt_up || e5.exp_seg == '0)) || + res; +`else + // Envelope rate counter reset / count. + rate_cnt_res = + (e5.adr == 'h0 && e5.rate_cnt == 8) || + (e5.adr == 'h1 && e5.rate_cnt == 31) || + (e5.adr == 'h2 && e5.rate_cnt == 62) || + (e5.adr == 'h3 && e5.rate_cnt == 94) || + (e5.adr == 'h4 && e5.rate_cnt == 148) || + (e5.adr == 'h5 && e5.rate_cnt == 219) || + (e5.adr == 'h6 && e5.rate_cnt == 266) || + (e5.adr == 'h7 && e5.rate_cnt == 312) || + (e5.adr == 'h8 && e5.rate_cnt == 391) || + (e5.adr == 'h9 && e5.rate_cnt == 976) || + (e5.adr == 'hA && e5.rate_cnt == 1953) || + (e5.adr == 'hB && e5.rate_cnt == 3125) || + (e5.adr == 'hC && e5.rate_cnt == 3906) || + (e5.adr == 'hD && e5.rate_cnt == 11719) || + (e5.adr == 'hE && e5.rate_cnt == 19531) || + (e5.adr == 'hF && e5.rate_cnt == 31250) || + res; + + // Exponential counter reset / count. + exp_cnt_res = + // Exponential decay. + (e5.exp_seg[0] && e5.exp_cnt == 2) || + (e5.exp_seg[1] && e5.exp_cnt == 4) || + (e5.exp_seg[2] && e5.exp_cnt == 8) || + (e5.exp_seg[3] && e5.exp_cnt == 16) || + (e5.exp_seg[4] && e5.exp_cnt == 30) || + // No exponential decay. + // Note that in this FPGA cycle we cannot use + // e5.attack, which is precalculated for the next + // SID cycle. e5.env_cnt_up = attack for this cycle. + (e5.prev_rate_cnt_res && (e5.env_cnt_up || e5.exp_seg == '0)) || + res; +`endif end always_ff @(posedge clk) begin + // Update counters. + if (cycle >= 6 && cycle <= 11) begin + { e5, e4, e3, e2, e1, e0 } <= { e4, e3, e2, e1, e0, e5 }; - // Counter control logic. - if (phase[sid::PHI2_PHI1]) begin - // Both the high and low 4 bits of the envelope counter are compared - // with the 4-bit sustain value. - sustain_cmp <= (env == { reg_i.sustain, reg_i.sustain }); - // Store gate value after any register write on PHI2. - prev_gate <= reg_i.gate; - env_cnt_up <= attack; - - // The counter stops counting once it has reached zero, - // and restarts once the gate goes high. - if (rise_00) begin - env_cnt_en <= 0; - end else if (rise_gate | res) begin - env_cnt_en <= 1; + // Counter control logic. + + // In the real SID, gate, FF, and 00 are latched by phi1, phi2, + // and are thus first available at the next cycle. Since the + // combinations of the signals don't depend on any other signals + // from the next cycle except for reset, they can be precalculated + // and stored for the next cycle. + e0.prev_gate <= ereg_5.gate; + e0.prev_FF <= exp_step[0]; + e0.prev_00 <= exp_step[6]; + + // The counter starts counting when the gate goes high or on reset, + // and stops counting once it has reached zero. + if (rise_gate | res) begin + e0.env_cnt_en <= 1; + end else if (rise_00) begin + e0.env_cnt_en <= 0; end - // The counter starts counting down after it has reached FF, - // or if the gate is low (decay / release). + // The counter starts counting down after it has reached FF (decay), + // or if the gate is low (release). // The counter starts counting up once the gate goes high (attack). - if (rise_FF | ~reg_i.gate) begin - attack <= 0; + if (rise_FF | ~ereg_5.gate) begin + e0.attack <= 0; end else if (rise_gate) begin - attack <= 1; + e0.attack <= 1; end + // Counting direction is delayed by another cycle. + e0.env_cnt_up <= e5.attack; + e0.prev_env_cnt_up <= e5.env_cnt_up; + // Invert counter bits on change of counting direction. + // Yet another cycle delay. + e0.env_cnt_inv <= e5.env_cnt_up ^ e5.attack; + + // Both the high and low 4 bits of the envelope counter are compared + // with the 4-bit sustain value. + e0.sustain <= (env == { ereg_5.sustain, ereg_5.sustain }) & ereg_5.gate & ~e5.attack; + // The counter counts up every time exp_cnt_res is set, as long as // the counter is not frozen at zero, and we're not in the sustain state. // In the real SID, the inversion and counter latch is done on different // clock phases. - env_cnt <= (env_cnt ^ { 8{env_cnt_inv} }) + { 7'b0, env_cnt_en & ~sustain & exp_cnt_res}; - end - - // Count up or down. - if (phase[sid::PHI2]) begin - // The counter bits are inverted when the counter direction changes. - env <= env_cnt ^ { 8{~env_cnt_up} }; - - // Invert counter bits on change of counter direction. - env_cnt_inv <= env_cnt_up ^ attack; - end + // The counter bits are inverted when the counting direction changes. + // The count is delayed by yet another cycle. + e0.env_cnt <= primed ? + (e5.env_cnt ^ { 8{e5.env_cnt_inv} }) + { 7'b0, e5.env_cnt_en & exp_cnt_res & ~e5.sustain } : + ENV_INIT; - // Multiplexer for rate period index (attack / decay / release). - if (phase[sid::PHI2_PHI1]) begin - // The reset flag for the rate counter below is latched by the next phi2. - // FIXME: Yosys doesn't understand case (expr) inside. - casez ({ reg_i.gate, attack }) - 2'b?1: adr <= reg_i.attack; - 2'b10: adr <= reg_i.decay; - 2'b00: adr <= reg_i.release_; - endcase - end - - if (LFSR_COUNTERS) begin +`ifndef RIPPLE_COUNTERS // LFSR counters were used in the real SID, see // https://github.com/libsidplayfp/SID_schematics/wiki/Envelope-Overview - // Envelope rate counter reset / count. - if (phase[sid::PHI2]) begin - prev_rate_cnt_res <= rate_cnt_res; - - rate_cnt_res <= - (adr == 'h0 && rate_cnt == 'h7f00) || - (adr == 'h1 && rate_cnt == 'h0006) || - (adr == 'h2 && rate_cnt == 'h003c) || - (adr == 'h3 && rate_cnt == 'h0330) || - (adr == 'h4 && rate_cnt == 'h20c0) || - (adr == 'h5 && rate_cnt == 'h6755) || - (adr == 'h6 && rate_cnt == 'h3800) || - (adr == 'h7 && rate_cnt == 'h500e) || - (adr == 'h8 && rate_cnt == 'h1212) || - (adr == 'h9 && rate_cnt == 'h0222) || - (adr == 'hA && rate_cnt == 'h1848) || - (adr == 'hB && rate_cnt == 'h59b8) || - (adr == 'hC && rate_cnt == 'h3840) || - (adr == 'hD && rate_cnt == 'h77e2) || - (adr == 'hE && rate_cnt == 'h7625) || - (adr == 'hF && rate_cnt == 'h0a93) || - res; - end - - if (phase[sid::PHI2_PHI1]) begin - if (rate_cnt_res) begin - rate_cnt <= '1; - end else begin - rate_cnt <= { rate_cnt[13:0], rate_cnt[14] ^ rate_cnt[13] }; - end + if (rate_cnt_res || !primed) begin + e0.rate_cnt <= '1; + end else begin + e0.rate_cnt <= { e5.rate_cnt[13:0], e5.rate_cnt[14] ^ e5.rate_cnt[13] }; end - // Exponential counter reset / count. - if (phase[sid::PHI2]) begin - exp_cnt_res <= - // Exponential decay. - (exp_seg[0] && exp_cnt == 'h1c) || - (exp_seg[1] && exp_cnt == 'h11) || - (exp_seg[2] && exp_cnt == 'h1b) || - (exp_seg[3] && exp_cnt == 'h08) || - (exp_seg[4] && exp_cnt == 'h0f) || - // No exponential decay. - (prev_rate_cnt_res && (attack || exp_seg == '0)) || - res; + if (exp_cnt_res || !primed) begin + e0.exp_cnt <= '1; + end else if (e5.prev_rate_cnt_res) begin + e0.exp_cnt <= { e5.exp_cnt[3:0], e5.exp_cnt[4] ^ e5.exp_cnt[2] }; end - if (phase[sid::PHI2_PHI1]) begin - if (exp_cnt_res) begin - exp_cnt <= '1; - end else if (prev_rate_cnt_res) begin - exp_cnt <= { exp_cnt[3:0], exp_cnt[4] ^ exp_cnt[2] }; - end - end - end else begin // !LFSR_COUNTERS - // LFSR counters were used in the real SID, however normal counters + // Latch reset signal for next cycle. + e0.prev_rate_cnt_res <= rate_cnt_res; +`else + // LFSR counters were used in the real SID, however ripple counters // don't necessarily use any more resources thanks to FPGA carry // logic, and they also simplify testing. - // Envelope rate counter reset / count. - if (phase[sid::PHI2]) begin - prev_rate_cnt_res <= rate_cnt_res; - - rate_cnt_res <= - (adr == 'h0 && rate_cnt == 8) || - (adr == 'h1 && rate_cnt == 31) || - (adr == 'h2 && rate_cnt == 62) || - (adr == 'h3 && rate_cnt == 94) || - (adr == 'h4 && rate_cnt == 148) || - (adr == 'h5 && rate_cnt == 219) || - (adr == 'h6 && rate_cnt == 266) || - (adr == 'h7 && rate_cnt == 312) || - (adr == 'h8 && rate_cnt == 391) || - (adr == 'h9 && rate_cnt == 976) || - (adr == 'hA && rate_cnt == 1953) || - (adr == 'hB && rate_cnt == 3125) || - (adr == 'hC && rate_cnt == 3906) || - (adr == 'hD && rate_cnt == 11719) || - (adr == 'hE && rate_cnt == 19531) || - (adr == 'hF && rate_cnt == 31250) || - res; + // The period of the LFSR15 is 2^15 - 1; wrap counter around at 2^15 - 2. + if (rate_cnt_res || e5.rate_cnt == 'h7FFE) begin + e0.rate_cnt <= 0; + end else begin + e0.rate_cnt <= e5.rate_cnt + 1; end - if (phase[sid::PHI2_PHI1]) begin - // The period of the LFSR15 is 2^15 - 1; wrap counter around at 2^15 - 2. - if (rate_cnt_res || rate_cnt == 'h7FFE) begin - rate_cnt <= 0; - end else begin - rate_cnt <= rate_cnt + 1; - end + // The period of the LFSR5 is 2^5 - 1; wrap counter around at 2^5 - 2. + // Note that this check is redundant, since the counter can only + // reach this value (30) when exp_seg[4] is set (famous last words). + if (exp_cnt_res || e5.exp_cnt == 'h1E) begin + e0.exp_cnt <= 0; + end else if (e5.prev_rate_cnt_res) begin + e0.exp_cnt <= e5.exp_cnt + 1; end - // Exponential counter reset / count. - if (phase[sid::PHI2]) begin - exp_cnt_res <= - // Exponential decay. - (exp_seg[0] && exp_cnt == 2) || - (exp_seg[1] && exp_cnt == 4) || - (exp_seg[2] && exp_cnt == 8) || - (exp_seg[3] && exp_cnt == 16) || - (exp_seg[4] && exp_cnt == 30) || - // No exponential decay. - (prev_rate_cnt_res && (attack || exp_seg == '0)) || - res; - end + // Latch reset signal for next cycle. + e0.prev_rate_cnt_res <= rate_cnt_res; +`endif - if (phase[sid::PHI2_PHI1]) begin - // The period of the LFSR5 is 2^5 - 1; wrap counter around at 2^5 - 2. - // Note that this check is redundant, since the counter can only - // reach this value (30) when exp_seg[4] is set (famous last words). - if (exp_cnt_res || exp_cnt == 'h1E) begin - exp_cnt <= 0; - end else if (prev_rate_cnt_res) begin - exp_cnt <= exp_cnt + 1; + // Set/reset selectors for exponential curve segments. + // In the real SID, the selectors are both latched and input to + // combinational logic for reset of the exponential counter at the + // same phi1 cycle. However since the reset signal is latched by + // phi2, we can postpone the use to the next cycle. + for (int i = 0; i < $bits(e0.exp_seg); i++) begin : seg + if (exp_step[i] | exp_step[i + 2] | res) begin + e0.exp_seg[i] <= 0; + end else if (exp_step[i + 1]) begin + e0.exp_seg[i] <= 1; end end + + // Multiplexer for rate period index (attack / decay / release). + // FIXME: Yosys doesn't understand case (expr) inside. + unique casez ({ ereg_5.gate, e5.attack }) + 2'b?1: e0.adr <= ereg_5.attack; + 2'b10: e0.adr <= ereg_5.decay; + 2'b00: e0.adr <= ereg_5.release_; + endcase + end + end + + always_ff @(posedge clk) begin + if (cycle == 11 && !primed) begin + // All counters and LFSRs are initialized. + primed <= 1; + end + end + +`ifdef VM_TRACE + // Latch voices for simulation. + /* verilator lint_off UNUSED */ + typedef struct packed { + sid::reg7_t exp_step; + logic rise_gate; + logic rise_FF; + logic rise_00; + sid::reg8_t env; + } sim_t; + + sim_t sim [6]; + envelope_t sim_state[6]; + + always_ff @(posedge clk) begin + if (cycle >= 6 && cycle <= 11) begin + sim[cycle - 6].exp_step <= exp_step; + sim[cycle - 6].rise_gate <= rise_gate; + sim[cycle - 6].rise_FF <= rise_FF; + sim[cycle - 6].rise_00 <= rise_00; + sim[cycle - 6].env <= env; + sim_state[cycle - 6] <= e5; end end + /* verilator lint_on UNUSED */ +`endif endmodule diff --git a/gateware/sid_filter.sv b/gateware/sid_filter.sv index 3169964..255d8f0 100644 --- a/gateware/sid_filter.sv +++ b/gateware/sid_filter.sv @@ -1,6 +1,6 @@ // ---------------------------------------------------------------------------- // This file is part of reDIP SID, a MOS 6581/8580 SID FPGA emulation platform. -// Copyright (C) 2022 Dag Lem +// Copyright (C) 2022 - 2023 Dag Lem // // This source describes Open Hardware and is licensed under the CERN-OHL-S v2. // @@ -46,15 +46,19 @@ endfunction module sid_filter #( - // The 6581 DC offset is approximately -1/18 of the dynamic range of one voice. + // FC offset for average 6581 filter curve. + localparam FC_OFFSET_6581 = 12'sh600, + // The 6581 mixer DC offset is approximately -1/18 of the dynamic range of one voice. localparam MIXER_DC_6581 = 24'(-(1 << 20)/18), localparam MIXER_DC_8580 = 24'(0), localparam PI = $acos(-1) )( - input logic clk, - input logic [2:0] stage, - input sid::filter_i_t filter_i, - output sid::s20_t audio_o + input logic clk, + input sid::cycle_t cycle, + input sid::filter_reg_t freg, + input sid::cfg_t cfg, + input sid::s22_t voice_i, + output sid::s20_t audio_o ); // MOS6581 filter cutoff: 200Hz - 24.2kHz (from cutoff curves below) @@ -89,9 +93,6 @@ module sid_filter #( // Extreme : fc_curve(x, 200, +760) // - sid::reg11_t fc; - sid::reg11_t fc_6581; - sid::s16_t w0_T_lsl17_8580; sid::s16_t w0_T_lsl17_6581; sid::s16_t w0_T_lsl17_6581_base = 0; @@ -111,12 +112,20 @@ module sid_filter #( // MOS8580 filter cutoff: 0 - 12.5kHz. // Max w0 = 2*pi*12500 = 78540 - // We us the same scaling factor for w0*T as above. + // We use the same scaling factor for w0*T as above. // The maximum value of the scaled w0*T is 1.048576/8*2*pi*12500 = 10294, // which is approximately 5 times the maximum fc (2^11 - 1 = 2047), // and may be calculated as 5*fc = 4*fc + fc (shift and add). // MOS6581 filter cutoff DAC output. + sid::reg11_t fc; + sid::reg11_t fc_6581; + + always_comb begin + // Filter cutoff register value. + fc = { freg.fc_hi, freg.fc_lo[2:0] }; + end + sid_dac #( .BITS(11) ) fc_dac ( @@ -146,12 +155,6 @@ module sid_filter #( end end - sid::s16_t vi = 0; - sid::s16_t vd = 0; - - sid::reg4_t mode = 0; - sid::reg4_t vol = 0; - // Hardware 16x16->32 multiply-add: // o = c +- (a * b) sid::s32_t o; @@ -172,122 +175,210 @@ module sid_filter #( // vlp = vlp - w0*vbp // vbp = vbp - w0*vhp // vhp = 1/Q*vbp - vlp - vi - sid::s16_t vlp, vlp2, vlp_next; - sid::s16_t vbp, vbp2, vbp_next; - sid::s16_t vhp, vhp2, vhp_next; + // + // (vlp, vbp, vhp) x 2 + sid::s16_t v5 = 0, v4 = 0, v3 = 0, v2 = 0, v1 = 0, v0 = 0; sid::s17_t dv; + sid::s16_t v_next; + + // Simplify by converting cycle number to pipeline stage number. + `define stage(cycle_1) (cycle == (cycle_1) || cycle == (cycle_1) + 5) + `define cycle_between(cycle_1, cycle_2) (cycle >= (cycle_1) && cycle <= (cycle_2)) + `define stage_between(cycle_1, cycle_2) (`cycle_between((cycle_1), (cycle_2)) || `cycle_between((cycle_1) + 5, (cycle_2) + 5)) + + // Mux and sum for direct audio path and filter input path. + // Each voice is 22 bits, i.e. the sum of four voices is 24 bits. + sid::s24_t vd = 0; + sid::s24_t vi = 0; + sid::reg4_t filt = 0; + sid::s24_t vd1 = 0; + + always_ff @(posedge clk) begin + if (`stage(1)) begin + vd <= 0; + vi <= 0; + filt <= freg.filt; + end else if (`stage_between(2, 5)) begin + // Direct audio path. + // 3 OFF (mode[3]) disconnects voice 3 from the direct audio path. + if (~(filt[0] || (freg.mode[3] && `stage(4)))) begin + vd <= vd + 24'(voice_i); + end + + // Filter path. + if (filt[0]) begin + vi <= vi + 24'(voice_i); + end + + filt <= filt >> 1; + end + + if (`stage(6)) begin + // Buffer input to master volume. + vd1 <= vd; + end + end + + // Mux and sum for filter outputs. + // Each filter output is 16 bits, i.e. the sum of three outputs is 17.5 + // bits. We assume that filter outputs are out of phase, so that we don't + // need more than 17 bits. + sid::s17_t vf = 0; + sid::reg4_t mode = 0; + + // Audio mixers. + always_ff @(posedge clk) begin + if (`stage(4)) begin + vf <= 0; + mode <= freg.mode; + end else if (`stage_between(5, 7)) begin + if (mode[0]) begin + vf <= vf + v_next; + end + + mode <= mode >> 1; + end + end - sid::reg11_t fc_x; + sid::reg4_t vol = 0; + sid::model_e model = sid::MOS6581; + sid::s12_t fc_offset; + sid::reg11_t fc_x = 0; always_comb begin - // Filter cutoff register value. - fc = { filter_i.regs.fc_hi, filter_i.regs.fc_lo[2:0] }; + // Filter cutoff offset. + fc_offset = cfg.fc_offset + FC_OFFSET_6581; // Intermediate results for filter. // Shifts -w0*vbp and -w0*vlp right by 17. - dv = 17'(o >>> 17); - vlp_next = clamp(vlp + dv); - vbp_next = clamp(vbp + dv); - vhp_next = clamp(o[10 +: 17]); + dv = 17'(o >>> 17); + + // Next hp or lp/bp. + v_next = clamp(`stage(7) ? o[10+:17] : 17'(v5) + dv); + + // Final result for audio output, at cycle 9 and 14. + // The effective width is 20 bits (4 bit volume * 16 bit audio). + audio_o = o[19:0]; end + // Calculation of filter outputs. TDM to use only one multiplier. always_ff @(posedge clk) begin - case (stage) - 1: begin - // MOS6581: w0 = filter curve - // 1.048576/8*fc_base is approximated by fc_base >> 3. - w0_T_lsl17_6581_base <= { 10'b0, filter_i.fc_base[8:3] }; - // We have to register fc_x in order to meet timing. - fc_x <= tanh_x_clamp(signed'(13'(fc_6581)) - filter_i.fc_offset); - - // MOS8580: w0 = 5*fc = 4*fc + fc - w0_T_lsl17_8580 <= { 3'b0, fc, 2'b0 } + { 5'b0, fc }; - - // MOS6581: 1/Q =~ ~res/8 (not used - op-amps are not ideal) - // MOS8580: 1/Q =~ 2^((4 - res)/8) - _1_Q_lsl10 <= (filter_i.model == sid::MOS6581) ? - _1_Q_6581_lsl10[filter_i.regs.res] : - _1_Q_8580_lsl10[filter_i.regs.res]; - - // Mux for filter path. - // Each voice is 22 bits, i.e. the sum of four voices is 24 bits. - vi <= 16'((((filter_i.regs.filt[0]) ? 24'(filter_i.voice1) : '0) + - ((filter_i.regs.filt[1]) ? 24'(filter_i.voice2) : '0) + - ((filter_i.regs.filt[2]) ? 24'(filter_i.voice3) : '0) + - ((filter_i.regs.filt[3]) ? 24'(filter_i.ext_in) : '0)) >>> 7); - - // Mux for direct audio path. - // 3 OFF (mode[3]) disconnects voice 3 from the direct audio path. - // We add in the mixer DC here, to save time in calculation of - // the final audio sum. - vd <= 16'((((filter_i.model == sid::MOS6581) ? - MIXER_DC_6581 : - MIXER_DC_8580) + - (filter_i.regs.filt[0] ? '0 : 24'(filter_i.voice1)) + - (filter_i.regs.filt[1] ? '0 : 24'(filter_i.voice2)) + - (filter_i.regs.filt[2] | - filter_i.regs.mode[3] ? '0 : 24'(filter_i.voice3)) + - (filter_i.regs.filt[3] ? '0 : 24'(filter_i.ext_in))) >>> 7); - - // Save settings to facilitate expedited filter pipeline setup. - mode <= filter_i.regs.mode; - vol <= filter_i.regs.vol; - end - 2: begin - // Read from BRAM. - w0_T_lsl17_6581 <= w0_T_lsl17_6581_tanh[tanh_x_mirror(fc_x)]; - end - 3: begin + if (`stage_between(5, 7)) begin + // (vlp, vbp, vhp) x 2 + { v5, v4, v3, v2, v1, v0 } <= { v4, v3, v2, v1, v0, v_next }; + end + + if (`stage(2)) begin + // MOS6581: w0 = filter curve + // 1.048576/8*fc_base is approximated by fc_base >> 3. + w0_T_lsl17_6581_base <= { 10'b0, cfg.fc_base[8:3] }; + // We have to register fc_x in order to meet timing. + fc_x <= tanh_x_clamp(signed'(13'(fc_6581)) - fc_offset); + + // MOS8580: w0 = 5*fc = 4*fc + fc + w0_T_lsl17_8580 <= { 3'b0, fc, 2'b0 } + { 5'b0, fc }; + + // MOS6581: 1/Q =~ ~res/8 (not used - op-amps are not ideal) + // MOS8580: 1/Q =~ 2^((4 - res)/8) + _1_Q_lsl10 <= (cfg.model == sid::MOS6581) ? + _1_Q_6581_lsl10[freg.res] : + _1_Q_8580_lsl10[freg.res]; + end + + if (`stage(3)) begin + // Read from BRAM. + w0_T_lsl17_6581 <= w0_T_lsl17_6581_tanh[tanh_x_mirror(fc_x)]; + + // Save model and volume for later stages. + model <= cfg.model; + vol <= freg.vol; + end + + unique0 case (cycle) + 4, 9: begin // vlp = vlp - w0*vbp // We first calculate -w0*vbp c <= 0; s <= 1'b1; - a <= (filter_i.model == sid::MOS6581) ? + a <= (model == sid::MOS6581) ? w0_T_lsl17_6581_base + w0_T_lsl17_6581_y0 + tanh_y_mirror(fc_x[10], w0_T_lsl17_6581) : - w0_T_lsl17_8580; // w0*T << 17 - b <= vbp; // vbp + w0_T_lsl17_8580; // w0*T << 17 + b <= v4; // vbp end - 4: begin - // Result for vlp ready. See calculation of vlp_next above. - { vlp, vlp2 } <= { vlp2, vlp_next }; + 5, 10: begin + // Result for vlp ready. // vbp = vbp - w0*vhp // We first calculate -w0*vhp - c <= 0; - s <= 1'b1; - // a <= a; // w0*T << 17 - b <= vhp; // vhp + // c <= 0; + // s <= 1'b1; + // a <= ... + b <= v3; // vhp end - 5: begin - // Result for vbp ready. See calculation of vbp_next above. - { vbp, vbp2 } <= { vbp2, vbp_next }; + 6, 11: begin + // Result for vbp ready. // vhp = 1/Q*vbp - vlp - vi - c <= -(32'(vlp2) + 32'(vi)) << 10; + // c <= -(32'(v0) + (32'(vi) >>> 7)) << 10; + c <= -(((32'(v0) << 7) + 32'(vi)) << 3); s <= 1'b0; - a <= 16'(_1_Q_lsl10); // 1/Q << 10 - b <= vbp_next; // vbp + a <= 16'(_1_Q_lsl10); // 1/Q << 10 + b <= v_next; // vbp end - 6: begin - // Result for vbp ready. See calculation of vhp_next above. - { vhp, vhp2 } <= { vhp2, vhp_next }; - + // 7, 12: Result for vhp ready + 8, 13: begin // Audio output: aout = vol*amix // In the real SID, the signal is inverted first in the mixer // op-amp, and then again in the volume control op-amp. c <= 0; - s <= 1'b0; - a <= { 12'b0, vol }; // Master volume - b <= clamp(17'(vd) + // Audio mixer / master volume input - (mode[0] ? 17'(vlp2) : '0) + - (mode[1] ? 17'(vbp2) : '0) + - (mode[2] ? 17'(vhp_next) : '0)); - end - 7: begin - // Final result for audio output ready. - // The effective width is 20 bits (4 bit volume * 16 bit audio). - audio_o <= o[19:0]; + // s <= 1'b0; + a <= { 12'b0, vol }; // Master volume + b <= clamp(17'((vd1 + // Audio mixer / master volume input + ((model == sid::MOS6581) ? + MIXER_DC_6581 : + MIXER_DC_8580) + ) >>> 7) + + vf); end endcase end + +`ifdef VM_TRACE + // Latch states for simulation. + /* verilator lint_off UNUSED */ + typedef struct packed { + sid::s24_t vd; + sid::s24_t vi; + sid::s16_t w0_T; + sid::reg11_t _1_Q; + sid::s16_t vhp; + sid::s16_t vbp; + sid::s16_t vlp; + sid::s17_t vf; + sid::s20_t vo; + } sim_t; + + sim_t sim[2]; + + always_ff @(posedge clk) begin + if (`stage(6)) begin + sim[cycle > 6].vd <= vd; + sim[cycle > 6].vi <= vi; + sim[cycle > 6].w0_T <= a; + sim[cycle > 6]._1_Q <= _1_Q_lsl10; + end + + if (`stage(8)) begin + sim[cycle > 8].vf <= vf; + sim[cycle > 8].vhp <= v0; + sim[cycle > 8].vbp <= v1; + sim[cycle > 8].vlp <= v2; + end + + if (`stage(9)) begin + sim[cycle > 9].vo <= audio_o; + end + end + /* verilator lint_on UNUSED */ +`endif endmodule diff --git a/gateware/sid_io.sv b/gateware/sid_io.sv index 48bbffe..85ed9b6 100644 --- a/gateware/sid_io.sv +++ b/gateware/sid_io.sv @@ -1,6 +1,6 @@ // ---------------------------------------------------------------------------- // This file is part of reDIP SID, a MOS 6581/8580 SID FPGA emulation platform. -// Copyright (C) 2022 Dag Lem +// Copyright (C) 2022 - 2023 Dag Lem // // This source describes Open Hardware and is licensed under the CERN-OHL-S v2. // @@ -30,7 +30,6 @@ module sid_io ( inout logic pad_res_n, inout logic [1:0] pad_pot, // Internal interfaces. - output logic phi2 = 0, output sid::bus_i_t bus_i, output sid::cs_t cs, input sid::reg8_t data_o, @@ -39,29 +38,23 @@ module sid_io ( ); // Control signals. - logic r_w_n; logic phi2_io; + logic phi2_x = 0; + logic phi2 = 0; logic phi1_io; // Inverted phi2 logic res_n_x; - - // Delayed phi2 for write enable. - logic phi2_prev = 0; + logic res = 0; // Output enable to be registered in the SB_IO OE register, separate from - // bus_i.oe used in sid_core.sv + // bus_i.oe used in sid_control.sv logic oe_io = 0; - logic we = 0; - logic oe = 0; - logic res = 0; - // POT signals. logic [1:0] charged_x; always_comb begin - bus_i.we = we; - bus_i.oe = oe; - bus_i.res = res; + bus_i.phi2 = phi2; + bus_i.res = res; end // The 6510 phi2 clock driver only weakly drives the clock line high. @@ -75,7 +68,7 @@ module sid_io ( // phi2 is configured as a simple input pin (not registered, i.e. without // any delay), so that the signal can be used to latch other signals, // which are stable until at least 10ns after the falling edge of phi2 - // (ref. 6510 datasheet). + // (ref. MOS6510 datasheet). SB_IO #( .PIN_TYPE (6'b0000_01), .PULLUP (1'b1) @@ -96,7 +89,7 @@ module sid_io ( .INPUT_CLK (clk), .D_IN_0 (res_n_x) ); - + // Hold other (registered) inputs at phi1, i.e. D-latch enable = phi2. // This allows us to read out the signals after the falling edge of phi2, // where the signals were stable. @@ -111,7 +104,7 @@ module sid_io ( .CLOCK_ENABLE (1'b1), `endif .INPUT_CLK (clk), - .D_IN_0 (r_w_n) + .D_IN_0 (bus_i.r_w_n) ); // Chip select, including extra pins. @@ -163,15 +156,13 @@ module sid_io ( always_ff @(posedge clk) begin // Bring phi2 into FPGA clock domain. - // phi2 is metastable, but can be used by the calling module - // for detection of the falling edge of phi2. - phi2 <= phi2_io; - phi2_prev <= phi2; + phi2_x <= phi2_io; + phi2 <= phi2_x; // The reset signal is already registered on the I/O input, // so we only add one extra register stage wrt. metastability. // Also OR in the system reset for PLL sync / BRAM powerup. - res <= ~res_n_x | rst; + res <= ~res_n_x | rst; // The data output must be held by the output enable for at least 10ns // after the falling edge of phi2 (ref. SID datasheet). This is ensured @@ -180,11 +171,7 @@ module sid_io ( // address conflict with expansion port cartridges. // Delay the start of the pin OE by ANDing with phi2, in order to avoid // any output glitches. - oe_io <= phi2_io & phi2 & r_w_n & ~cs.cs_n; - oe <= phi2 & r_w_n; - - // Write enable must be held at the detected falling edge of phi2. - we <= phi2_prev & ~r_w_n; + oe_io <= phi2_io & phi2 & bus_i.r_w_n & ~cs.cs_n; end // The MOS6581 datasheet specifies a minimum POT sink current of 500uA. @@ -196,9 +183,9 @@ module sid_io ( .RGB2_CURRENT ("0b000000") // 0mA /IO1, uses SB_IO only ) rgba_drv ( .CURREN (1'b1), - .RGBLEDEN (pot_o.discharge), - .RGB0PWM (1'b1), - .RGB1PWM (1'b1), + .RGBLEDEN (1'b1), + .RGB0PWM (pot_o.discharge), + .RGB1PWM (pot_o.discharge), .RGB0 (pad_pot[0]), .RGB1 (pad_pot[1]) ); @@ -210,14 +197,14 @@ module sid_io ( // can still be used instead of SB_IO_OD. // No output, registered input. SB_IO #( - .PIN_TYPE (6'b0000_00) + .PIN_TYPE (6'b0000_00) ) io_pot[1:0] ( - .PACKAGE_PIN (pad_pot), + .PACKAGE_PIN (pad_pot), `ifdef VERILATOR - .CLOCK_ENABLE (1'b1), + .CLOCK_ENABLE (1'b1), `endif - .INPUT_CLK (clk), - .D_IN_0 (charged_x) + .INPUT_CLK (clk), + .D_IN_0 (charged_x) ); always_ff @(posedge clk) begin diff --git a/gateware/sid_pkg.sv b/gateware/sid_pkg.sv index 4b8c2ce..05be905 100644 --- a/gateware/sid_pkg.sv +++ b/gateware/sid_pkg.sv @@ -1,6 +1,6 @@ // ---------------------------------------------------------------------------- // This file is part of reDIP SID, a MOS 6581/8580 SID FPGA emulation platform. -// Copyright (C) 2022 Dag Lem +// Copyright (C) 2022 - 2023 Dag Lem // // This source describes Open Hardware and is licensed under the CERN-OHL-S v2. // @@ -45,22 +45,13 @@ package sid; typedef logic signed [11:0] s12_t; // tanh_x offset typedef logic signed [10:0] s11_t; // tanh_x clamped - // FIXME: Currently the array must be encapsulated in a struct, - // otherwise Yosys miscalculates its size. - /* verilator lint_off LITENDIAN */ typedef struct packed { s24_t left; s24_t right; } audio_t; - /* verilator lint_on LITENDIAN */ - typedef enum { - PHI2, - PHI2_PHI1, - PHI1 - } phase_e; - - typedef logic [PHI1:0] phase_t; + // Pipeline cycles. + typedef logic [3:0] cycle_t; typedef enum logic [0:0] { MOS6581, @@ -84,7 +75,7 @@ package sid; typedef struct packed { model_e model; - addr_t addr; // Only used for SID #2. + addr_t addr; // Only used for SID 2. reg9_t fc_base; s11_t fc_offset; } cfg_t; @@ -92,8 +83,8 @@ package sid; typedef struct packed { reg5_t addr; reg8_t data; - logic we; - logic oe; + logic phi2; + logic r_w_n; logic res; } bus_i_t; @@ -112,6 +103,8 @@ package sid; logic discharge; } pot_o_t; + // Control registers. + typedef struct packed { // FREQ Lo/Hi reg8_t freq_lo; @@ -119,25 +112,33 @@ package sid; // PW Lo/Hi reg8_t pw_lo; reg8_t pw_hi; - // Control Reg (upper 7 bits) - logic noise; - logic pulse; - logic sawtooth; - logic triangle; - logic test; - logic ring_mod; - logic sync; + } freq_pw_t; + + // Control Reg (upper 7 bits). + typedef struct packed { + logic noise; + logic pulse; + logic sawtooth; + logic triangle; + logic test; + logic ring_mod; + logic sync; + } control_t; + + typedef struct packed { + freq_pw_t freq_pw; + control_t control; } waveform_reg_t; typedef struct packed { - // Control Reg (lower 1 bit) + // Control Reg (lower 1 bit). logic gate; - // Attack/Decay + // Attack/Decay. reg4_t attack; reg4_t decay; - // Sustain/Release + // Sustain/Release. reg4_t sustain; - reg4_t release_; // release is a Verilog keyword + reg4_t release_; // release is a Verilog keyword. } envelope_reg_t; typedef struct packed { @@ -157,88 +158,17 @@ package sid; reg4_t vol; } filter_reg_t; - // Write-only registers for the full 5 bit address space. - /* verilator lint_off LITENDIAN */ - typedef struct packed { - // voice_reg_t [0:2] voice; - voice_reg_t voice1; - voice_reg_t voice2; - voice_reg_t voice3; - filter_reg_t filter; - - // Extra write-only registers, not present in the real SID. - // These are included in order to facilitate configuration by - // the writing of magic bytes. - logic [0:6][7:0] magic; - } write_reg_t; - // FIXME: Currently the array must be encapsulated in a struct, // otherwise Yosys miscalculates its size. + /* verilator lint_off LITENDIAN */ typedef struct packed { logic [0:1][7:0] xy; } pot_reg_t; + /* verilator lint_on LITENDIAN */ typedef struct packed { pot_reg_t pot; reg8_t osc3; reg8_t env3; - } read_reg_t; - - // Byte addressable write-only registers. - typedef union packed { - logic [0:31][7:0] bytes; - write_reg_t regs; - } reg_i_t; - - // Byte addressable read-only registers. - typedef union packed { - // logic ['h19:'h1c][7:0] bytes; - logic [0:3][7:0] bytes; - read_reg_t regs; - } reg_o_t; - /* verilator lint_on LITENDIAN */ - - // Oscillator synchronization. - typedef struct packed { - logic msb; - logic synced; - } sync_t; - - // Input to waveform mixer. - typedef struct packed { - // Noise, pulse, sawtooth, triangle. - reg4_t selector; - reg8_t noise; - logic pulse; - reg12_t saw_tri; - } waveform_i_t; - - // Input to waveform mixer / voice DCA. - typedef struct packed { - waveform_i_t waveform; - reg8_t envelope; - } voice_i_t; - - // Digital output from SID core. - typedef struct packed { - filter_reg_t filter_regs; - voice_i_t voice1; - voice_i_t voice2; - voice_i_t voice3; - } core_o_t; - - // Input to audio filter / audio output stage. - typedef struct packed { - model_e model; - filter_reg_t regs; - // Filter cutoff curve parameters. - reg9_t fc_base; // Base cutoff frequency in Hz - s12_t fc_offset; // Final FC register offset for curve shifting - // Input signals. - s22_t voice1; - s22_t voice2; - s22_t voice3; - s22_t ext_in; - } filter_i_t; - + } misc_reg_t; endpackage diff --git a/gateware/sid_pot.sv b/gateware/sid_pot.sv index 7236efd..f4ec3d4 100644 --- a/gateware/sid_pot.sv +++ b/gateware/sid_pot.sv @@ -1,6 +1,6 @@ // ---------------------------------------------------------------------------- // This file is part of reDIP SID, a MOS 6581/8580 SID FPGA emulation platform. -// Copyright (C) 2022 Dag Lem +// Copyright (C) 2022 - 2023 Dag Lem // // This source describes Open Hardware and is licensed under the CERN-OHL-S v2. // @@ -21,10 +21,10 @@ module sid_pot #( localparam INIT_POT = 0 )( input logic clk, - input sid::phase_t phase, - output sid::pot_reg_t pot_reg = '0, + input sid::cycle_t cycle, input sid::pot_i_t pot_i, - output sid::pot_o_t pot_o + output sid::pot_o_t pot_o, + output sid::pot_reg_t pot = '0 ); // 9 bit counter, used by both POTX and POTY. @@ -46,7 +46,7 @@ module sid_pot #( end always_ff @(posedge clk) begin - if (phase[sid::PHI2_PHI1]) begin + if (cycle == 1) begin // Count phi1 cycles. pot_cnt <= pot_cnt + 1; end @@ -57,17 +57,17 @@ module sid_pot #( // POT register once the input is high, or the counter = FF. // In the real SID, the register load is delayed by one cycle, however // it is not necessary to emulate this accurately. - for (genvar i = 0; i < 2; i++) begin : pot + for (genvar i = 0; i < 2; i++) begin : pots always_ff @(posedge clk) begin - if (phase[sid::PHI2_PHI1]) begin + if (cycle == 1) begin // Reset/set position detection status / load POT register. if (pot_o.discharge) begin pos_det[i] <= 0; end else if (~pos_det[i] & (pot_i.charged[i] | pos_FF)) begin // Load POT registers when the POT position is detected, // once within every 512 cycles. - pos_det[i] <= 1; - pot_reg.xy[i] <= pot_cnt[7:0]; + pos_det[i] <= 1; + pot.xy[i] <= pot_cnt[7:0]; end end end diff --git a/gateware/sid_voice.sv b/gateware/sid_voice.sv index 6a3ad88..40fb821 100644 --- a/gateware/sid_voice.sv +++ b/gateware/sid_voice.sv @@ -1,6 +1,6 @@ // ---------------------------------------------------------------------------- // This file is part of reDIP SID, a MOS 6581/8580 SID FPGA emulation platform. -// Copyright (C) 2022 Dag Lem +// Copyright (C) 2022 - 2023 Dag Lem // // This source describes Open Hardware and is licensed under the CERN-OHL-S v2. // @@ -16,209 +16,120 @@ `default_nettype none -`include "sid_waveform_PST.svh" -`include "sid_waveform__ST.svh" - // Waveform selector, voice DCA (digitally controlled amplifier). // Modeling of non-linearities in MOS6581 waveform and envelope DACs. module sid_voice #( - localparam WAVEFORM_DC_6581 = -16'sh380, // OSC3 = 'h38 at 5.94V. - localparam WAVEFORM_DC_8580 = -16'sh800, // No DC offsets in the MOS8580. + localparam WAVEFORM_DC_6581 = -16'sh380, // OSC3 = 'h38 at 5.94V. + localparam WAVEFORM_DC_8580 = -16'sh800, // No DC offsets in the MOS8580. localparam VOICE_DC_6581 = 32'('h340*'hff), // Measured from samples. - localparam VOICE_DC_8580 = 32'h0, // No DC offsets in the MOS8580. - localparam WF_0_TTL_6581 = 13'd200, // Waveform 0 TTL ~200ms - localparam WF_0_TTL_8580 = 13'd5000 // Waveform 0 TTL ~5s + localparam VOICE_DC_8580 = 32'h0 // No DC offsets in the MOS8580. )( - input logic clk, - input logic tick_ms, - input logic active, - input sid::model_e model, - input sid::voice_i_t voice_i, - // The outputs are delayed by 1 cycle. - output sid::s22_t voice_o, - output sid::reg8_t osc_o + input logic clk, + input sid::cycle_t cycle, + input logic[1:0] model, + input sid::reg12_t wav, + input sid::reg8_t env, + output sid::s22_t dca ); - // Registered values for pipelined calculation. - sid::model_e model_prev = sid::MOS6581; - logic pulse_prev = 0; - sid::reg4_t selector_prev = 0; - - // Pre-calculated waveforms for waveform selection. - sid::reg12_t norm; // Selected regular waveform - sid::reg12_t norm_6581; // Output from 6581 waveform DAC - sid::reg12_t norm_dac = 0; // 6581 / 8580 result - sid::reg12_t norm_osc = 0; // For OSC3 - sid::reg12_t wave_osc; // Resulting waveform - sid::reg8_t comb; // Selected combined waveform - sid::reg8_t pst = 0; // Combined waveforms - sid::reg8_t ps__6581 = 0; - sid::reg8_t ps__8580 = 0; - sid::reg8_t p_t_6581 = 0; - sid::reg8_t p_t_8580 = 0; - sid::reg8_t _st = 0; - - // Output from non-linear 6581 envelope DAC. - sid::reg8_t env_6581; + // Keep track of the current SID model. + logic model_6; - // Inputs to voice DCA. - sid::s32_t voice_DC; - sid::s16_t wave_dac; - sid::s16_t env_dac = 0; - // Output from voice DCA. - /* verilator lint_off UNUSED */ - sid::s32_t voice_res; - /* verilator lint_on UNUSED */ - - // Waveform 0 value and age. - /* verilator lint_off LITENDIAN */ - // FIXME: Yosys doesn't support multidimensional packed arrays outside - // of structs, nor arrays of structs. - struct packed { - logic [0:5][12:0] age; - logic [0:5][11:0] value; - } waveform_0; - /* verilator lint_on LITENDIAN */ - - logic [12:0] waveform_0_age = 0; - logic [11:0] waveform_0_value; - logic waveform_0_faded; - - // Combined waveform lookup tables. - sid::reg8_t sid_waveform_PS__6581[2048]; - sid::reg8_t sid_waveform_PS__8580[4096]; - sid::reg8_t sid_waveform_P_T_6581[2048]; - sid::reg8_t sid_waveform_P_T_8580[2048]; + always_comb begin + model_6 = model[cycle >= 10]; + end // MOS6581 waveform DAC output. + sid::reg12_t wav_6581; + sid::reg12_t wav_8580 = 0; + sid_dac #( .BITS (12) ) waveform_dac ( - .vin (norm), - .vout (norm_6581) + .vin (wav_8580), + .vout (wav_6581) ); // MOS6581 envelope DAC output. + sid::reg8_t env_6581; + sid::reg8_t env_8580 = 0; + sid_dac #( .BITS (8) ) envelope_dac ( - .vin (voice_i.envelope), + .vin (env_8580), .vout (env_6581) ); + always_ff @(posedge clk) begin + if (cycle >= 6 && cycle <= 11) begin + wav_8580 <= wav; + env_8580 <= env; + end + end + + // Registered inputs to voice DCA. + sid::s32_t dca_DC = 0; + sid::s16_t wav_dac = 0; + sid::s16_t env_dac = 0; + + // Output from voice DCA. + /* verilator lint_off UNUSED */ + sid::s32_t dca_out; + /* verilator lint_on UNUSED */ + // Voice DCA (digitally controlled amplifier). - // voice_res = voice_DC + waveform*envelope + // dca_out = dca_DC + wav_dac*env_dac // The result fits in 22 bits. muladd voice_dca ( - .c (voice_DC), + .c (dca_DC), .s (1'b0), - .a (wave_dac), + .a (wav_dac), .b (env_dac), - .o (voice_res) + .o (dca_out) ); + always_ff @(posedge clk) begin + if (cycle >= 7 && cycle <= 12) begin + // Setup for voice DCA multiply-add, ready on cycle 2. + dca_DC <= (model_6 == sid::MOS6581) ? + VOICE_DC_6581 : + VOICE_DC_8580; + + wav_dac <= (model_6 == sid::MOS6581) ? + 16'(wav_6581) + WAVEFORM_DC_6581 : + 16'(wav_8580) + WAVEFORM_DC_8580; + + env_dac <= (model_6 == sid::MOS6581) ? + 16'(env_6581) : + 16'(env_8580); + end + end + always_comb begin - // With respect to the oscillator, the waveform cycle delays are: - // * saw_tri: 0 (6581) / 1 (8580) - // * pulse: 1 - // * noise: 2 - - // Regular waveforms are computed on cycle 1. These are passed through - // the non-linear MOS6581 waveform DAC. - // The result for pulse is identical, but we include it for simplicity. - // All combined waveforms which include noise output zero after a few - // cycles. - unique case (voice_i.waveform.selector) - 'b1000: norm = { voice_i.waveform.noise, 4'b0 }; - 'b0100: norm = { 12{voice_i.waveform.pulse} }; - 'b0010: norm = voice_i.waveform.saw_tri; - 'b0001: norm = { voice_i.waveform.saw_tri[10:0], 1'b0 }; - 'b0000: norm = waveform_0.value[0]; - default: norm = 0; - endcase - - // Final waveform selection on cycle 2. - // All inputs to the combinational logic are from cycle 1. - unique case (selector_prev) - 'b0111: comb = pst & { 8{pulse_prev} }; - 'b0110: comb = ((model_prev == sid::MOS6581) ? ps__6581 : ps__8580) & { 8{pulse_prev} }; - 'b0101: comb = ((model_prev == sid::MOS6581) ? p_t_6581 : p_t_8580) & { 8{pulse_prev} }; - 'b0011: comb = _st; - default: comb = 0; - endcase - - // Setup for voice DCA multiply-add, ready on cycle 2. - voice_DC = (model_prev == sid::MOS6581) ? - VOICE_DC_6581 : - VOICE_DC_8580; - wave_dac = 16'({ norm_dac | { comb, 4'b0 } }) + - ((model_prev == sid::MOS6581) ? - WAVEFORM_DC_6581 : - WAVEFORM_DC_8580); - - wave_osc = norm_osc | { comb, 4'b0 }; - - // Update of waveform 0 for next cycle. - waveform_0_faded = waveform_0_age == ((model_prev == sid::MOS6581) ? WF_0_TTL_6581 : WF_0_TTL_8580); - waveform_0_value = selector_prev == 'b0000 && waveform_0_faded ? 0 : wave_osc; - - // The outputs are delayed by 1 cycle. - osc_o = wave_osc[11 -: 8]; // The DCA output fits in 22 bits. - voice_o = voice_res[21:0]; + // Register output. + dca = dca_out[21:0]; end - always_ff @(posedge clk) begin - if (active) begin - // Cycle 1: Register all candidate waveforms, to synchronize with - // output from BRAM, and deliver one result per cycle from a pipeline. - - // For pipelined selection of waveform. - model_prev <= model; - pulse_prev <= voice_i.waveform.pulse; - selector_prev <= voice_i.waveform.selector; - - // Regular waveforms, passed through the non-linear MOS6581 - // waveform DAC. - norm_dac <= (model == sid::MOS6581) ? - norm_6581 : - norm; - // For OSC3. - norm_osc <= norm; - - // Combined waveforms. - // These aren't accurately modeled in the analog domain, so passing - // them through the non-linear MOS6581 DAC wouldn't make much sense. - // Skipping this avoids further muxing, and speeds up the design. - // NB! Waveform 0 is currently passed through the DAC, even if it - // originated as a combined waveform. - pst <= sid_waveform_PST(model, voice_i.waveform.saw_tri); - ps__6581 <= sid_waveform_PS__6581[voice_i.waveform.saw_tri[10:0]]; - ps__8580 <= sid_waveform_PS__8580[voice_i.waveform.saw_tri]; - p_t_6581 <= sid_waveform_P_T_6581[voice_i.waveform.saw_tri[10:0]]; - p_t_8580 <= sid_waveform_P_T_8580[voice_i.waveform.saw_tri[10:0]]; - _st <= sid_waveform__ST(model, voice_i.waveform.saw_tri); - - // Update of waveform 0. - waveform_0_age <= voice_i.waveform.selector == 'b0000 ? waveform_0.age[0] + { 12'b0, tick_ms } : 0; - waveform_0.age <= { waveform_0.age[1:5], waveform_0_age }; - waveform_0.value <= { waveform_0.value[1:5], waveform_0_value }; +`ifdef VM_TRACE + // Latch voices for simulation. + /* verilator lint_off UNUSED */ + typedef struct packed { + sid::s16_t wav_dac; + sid::s16_t env_dac; + sid::s22_t dca; + } sim_t; - // Setup for voice DCA multiply-add, ready on cycle 2. - env_dac <= (model == sid::MOS6581) ? - 16'(env_6581) : - 16'(voice_i.envelope); - end - end + sim_t sim[6]; - // od -An -tx1 -v reSID/src/wave6581_PS_.dat | head -128 | cut -b2- > sid_waveform_PS__6581.hex - // od -An -tx1 -v reSID/src/wave8580_PS_.dat | cut -b2- > sid_waveform_PS__8580.hex - // od -An -tx1 -v reSID/src/wave6581_P_T.dat | head -128 | cut -b2- > sid_waveform_P_T_6581.hex - // od -An -tx1 -v reSID/src/wave8580_P_T.dat | head -128 | cut -b2- > sid_waveform_P_T_8580.hex - initial begin - $readmemh("sid_waveform_PS__6581.hex", sid_waveform_PS__6581); - $readmemh("sid_waveform_PS__8580.hex", sid_waveform_PS__8580); - $readmemh("sid_waveform_P_T_6581.hex", sid_waveform_P_T_6581); - $readmemh("sid_waveform_P_T_8580.hex", sid_waveform_P_T_8580); + always_ff @(posedge clk) begin + if (cycle >= 8 && cycle <= 13) begin + sim[cycle - 8].wav_dac <= wav_dac; + sim[cycle - 8].env_dac <= env_dac; + sim[cycle - 8].dca <= dca; + end end + /* verilator lint_on UNUSED */ +`endif endmodule diff --git a/gateware/sid_waveform.sv b/gateware/sid_waveform.sv index 0edb785..192d026 100644 --- a/gateware/sid_waveform.sv +++ b/gateware/sid_waveform.sv @@ -1,6 +1,6 @@ // ---------------------------------------------------------------------------- // This file is part of reDIP SID, a MOS 6581/8580 SID FPGA emulation platform. -// Copyright (C) 2022 Dag Lem +// Copyright (C) 2022 - 2023 Dag Lem // // This source describes Open Hardware and is licensed under the CERN-OHL-S v2. // @@ -16,49 +16,58 @@ `default_nettype none +`include "sid_waveform_PST.svh" +`include "sid_waveform__ST.svh" + module sid_waveform #( - // Default to init, since the oscillator stays at 'h555555 after reset. - localparam INIT_OSC = 1, - // Default to init, since the noise LFSR stays at 'h7ffffe after reset. - // FIXME: Is the initial value 'h7fffff possibly caused by a long reset? - localparam INIT_NOISE = 1, + // Even bits are high on powerup, and there is no reset. + localparam OSC_INIT = { 12{2'b01} }, // Time for noise LFSR to be filled with 1 bits when reset or test is held. - localparam NOISE_TTL_6581 = 14'd33, - localparam NOISE_TTL_8580 = 14'd9765 + localparam NOISE_TTL_6581 = 14'd33, // ~30ms + localparam NOISE_TTL_8580 = 14'd9765, // ~10s + // Time for waveform 0 to fade out. + localparam WF_0_TTL_6581 = 13'd200, // ~200ms + localparam WF_0_TTL_8580 = 13'd5000 // ~5s )( - input logic clk, - input logic tick_ms, - input logic res, - input sid::model_e model, - input sid::phase_t phase, - input sid::waveform_reg_t reg_i, - input sid::sync_t sync_i, - output sid::sync_t sync_o, - output sid::waveform_i_t out + input logic clk, + input logic tick_ms, + input sid::cycle_t cycle, + input logic res, + input logic[1:0] model, + input sid::freq_pw_t freq_pw_1, + input sid::control_t control_3, + input sid::control_t control_4, + input sid::control_t control_5, + input logic [2:0] test, + input logic [2:0] sync, + output sid::reg12_t wav ); - // Phase-accumulating oscillator. - // Even bits are high on powerup, and there is no reset. - // Since initial values != 0 require LUTs, we make this configurable. - sid::reg24_t osc = INIT_OSC ? { 12{2'b01} } : 0; + // Initialization flag. + logic primed = 0; + + // ------------------------------------------------------------------------ + // Oscillators + // ------------------------------------------------------------------------ + // Phase-accumulating oscillators. Two extra pipeline stages for synchronization. + sid::reg24_t o7 = 0, o6 = 0, o5 = 0, o4 = 0, o3 = 0, o2 = 0, o1 = 0, o0 = 0; sid::reg24_t osc_next; - logic msb_i_prev = 0; - logic msb_i; - logic sync_res; - - // Waveforms. - logic osc19_prev = 0; - logic nres; - logic nclk; - logic nres_prev = 0; - logic nclk_prev = 0; - sid::reg23_t noise = INIT_NOISE ? '1 : '0; - logic pulse = 0; - logic tri_xor = 0; - sid::reg12_t saw_tri = 0; - logic [13:0] noise_age = 0; + // Inter-oscillator synchronization. + logic o7_msb_up; + logic o0_msb_up = 0, o1_msb_up = 0; + logic o7_synced, o0_synced, o1_synced; + logic o7_reset, o0_reset, o1_reset; + // Latched signals for noise and pulse. + logic [7:0] osc19_prev = '0; + logic [7:0] osc19_prev_up = '0; + logic [7:0] pulse = '0; + logic pulse_next; always_comb begin + // Constant value / ripple carry add can in theory be combined in single iCE40 LCs. + osc_next = primed ? o7 + { 8'b0, freq_pw_1.freq_hi, freq_pw_1.freq_lo } : OSC_INIT; + o7_msb_up = ~o7[23] & osc_next[23]; + // A sync source will normally sync its destination when the MSB of the // sync source rises. However if the sync source is itself synced on the // same cycle, its MSB is zeroed, and the destination will not be synced. @@ -66,102 +75,339 @@ module sid_waveform #( // circuit, which takes the form of a ring oscillator when all three // oscillators are synced on the same cycle. Here, like in reSID, no // oscillator will be synced in this special case of the special case. - osc_next = osc + { 8'b0, reg_i.freq_hi, reg_i.freq_lo }; - sync_o.msb = osc_next[23]; - sync_o.synced = reg_i.test | (reg_i.sync & ~msb_i_prev & sync_i.msb); - // Note that we cannot include sync_i.synced in the expressions above, - // since this would introduce a circular dependency. - msb_i = sync_i.msb & ~sync_i.synced; - sync_res = reg_i.test | (reg_i.sync & ~msb_i_prev & msb_i); + // + // We sync all oscillators as soon as possible, in order to get + // correct msbs for ring modulation. + // + // At cycle 4 and 7 (SID 1 / SID 2): + // voice 1 osc = o1 + // voice 2 osc = o0 + // voice 3 osc = o7 + // + // ---------<---------- ---------<---------- + // | | = | | + // -> v1 -> v2 -> v3 -> -> o1 -> o0 -> o7 -> + // + o7_synced = test[0] || (sync[0] && o0_msb_up); + o0_synced = test[1] || (sync[1] && o1_msb_up); + o1_synced = test[2] || (sync[2] && o7_msb_up); - // Noise LFSR reset and clock. - nres = res | reg_i.test; - nclk = ~(nres | (~osc19_prev & osc[19])); - - // Waveform output, to waveform mixer / DAC. - out.selector = { reg_i.noise, reg_i.pulse, reg_i.sawtooth, reg_i.triangle }; - out.noise = { noise[20], noise[18], noise[14], noise[11], noise[9], noise[5], noise[2], noise[0] }; - out.pulse = pulse; - out.saw_tri = saw_tri; + o7_reset = test[0] || (sync[0] && o0_msb_up && !o0_synced); + o0_reset = test[1] || (sync[1] && o1_msb_up && !o1_synced); + o1_reset = test[2] || (sync[2] && o7_msb_up && !o7_synced); + + // The pulse width comparison is done at phi2, before the oscillator + // is updated at phi1. Thus the pulse waveform is delayed by one cycle + // with respect to the oscillator. + pulse_next = (o7[23-:12] >= { freq_pw_1.pw_hi[3:0], freq_pw_1.pw_lo }) | test[0]; end always_ff @(posedge clk) begin - // Update oscillator. - // In the real SID, an intermediate sum is latched by phi2, and this - // sum is simultaneously synced, output to waveform generation bits, - // and written back to osc on phi1. - if (phase[sid::PHI2_PHI1]) begin - if (sync_res) begin - osc <= '0; - end else begin - osc <= osc_next; - end + if (cycle >= 2 && cycle <= 9) begin + // Update oscillators. + // In the real SID, an intermediate sum is latched by phi2, and this + // sum is simultaneously synced, output to waveform generation bits, + // and written back to osc on phi1. + { o7, o6, o5, o4, o3, o2, o1, o0 } <= { o6, o5, o4, o3, o2, o1, o0, osc_next }; + { o1_msb_up, o0_msb_up } <= { o0_msb_up, o7_msb_up }; - // The input oscillator MSB is latched by phi2. - msb_i_prev <= msb_i; + // Save previous rise of OSC bit 19 for generation of the noise waveform. + // OSC bit 19 is read before the update of OSC at phi1, i.e. + // it's delayed by one cycle. + osc19_prev <= { osc19_prev[6:0], o7[19] }; + osc19_prev_up <= { osc19_prev_up[6:0], ~osc19_prev[7] & o7[19] }; + + // Pulse. + pulse <= { pulse[6:0], pulse_next }; - // The sawtooth and triangle waveforms are constructed from the - // upper 12 bits of the oscillator. When sawtooth is not selected, - // and the MSB is high, the lower 11 of these 12 bits are - // inverted. When triangle is selected, the lower 11 bits are - // shifted up to produce the final output. The MSB may be modulated - // by the preceding oscillator for ring modulation. - tri_xor <= ~reg_i.sawtooth & ((reg_i.ring_mod & ~msb_i) ^ (osc_next[23] & ~sync_res)); + if (cycle == 4 || cycle == 7) begin + // Reset of oscillators by test bit or synchronization. + if (o7_reset) begin + o0 <= 0; + end + + if (o0_reset) begin + o1 <= 0; + end + + if (o1_reset) begin + o2 <= 0; + end + end end + end - // In the 8580, sawtooth / triangle is latched by phi2, and is thus - // delayed by one SID cycle. - if ((model == sid::MOS6581 && phase[sid::PHI1]) || - (model == sid::MOS8580 && phase[sid::PHI2_PHI1])) - begin - saw_tri <= { osc[23], osc[22:12] ^ { 11{tri_xor} } }; + // ------------------------------------------------------------------------ + // Waveform generators + // ------------------------------------------------------------------------ + // Keep track of the current SID model. + logic model_5 = 0, model_4 = 0, model_3; + + always_comb begin + model_3 = model[cycle >= 7]; + end + + always_ff @(posedge clk) begin + if (cycle >= 4 && cycle <= 10) begin + { model_5, model_4 } <= { model_4, model_3 }; end + end - // Noise. - if (phase[sid::PHI2_PHI1]) begin - // OSC bit 19 is read before the update of OSC at PHI2_PHI1, i.e. - // it's delayed by one cycle. - osc19_prev <= osc[19]; - nres_prev <= nres; - nclk_prev <= nclk; + // Pulse is generated earlier. + + // Noise. + typedef struct packed { + sid::reg23_t lfsr; + logic [13:0] age; + logic nres_prev; + logic nclk_prev; + } noise_t; + + noise_t n5 = '0, n4 = '0, n3 = '0, n2 = '0, n1 = '0, n0 = '0; + logic nres; + logic nclk; + logic nset; + sid::reg8_t noise; + + // Noise. + always_comb begin + // Noise LFSR reset and clock. + nres = res | control_3.test; + nclk = ~(nres | osc19_prev_up[1]); + nset = (n5.age == ((model_3 == sid::MOS6581) ? + NOISE_TTL_6581 : + NOISE_TTL_8580)); + + noise = { n0.lfsr[20], n0.lfsr[18], n0.lfsr[14], + n0.lfsr[11], n0.lfsr[9], n0.lfsr[5], n0.lfsr[2], n0.lfsr[0] }; + end - if (~nclk) begin + always_ff @(posedge clk) begin + if (cycle >= 4 && cycle <= 9) begin + // The noise LFSR stays at 'h7ffffe after reset (clocked on reset release). + // FIXME: Check whether this is also true for the MOS8580. + // n0 is updated below. + { n5, n4, n3, n2, n1, n0 } <= { n4, n3, n2, n1, n0, n5 }; + + n0.nres_prev <= nres; + n0.nclk_prev <= nclk; + + if (~nclk || !primed) begin // LFSR shift phase 1. - if (noise_age == (model == sid::MOS6581 ? - NOISE_TTL_6581 : - NOISE_TTL_8580)) - begin + if (nset || !primed) begin // Reset LFSR. - noise <= '1; + n0.lfsr <= '1; end else begin - noise_age <= noise_age + { 13'b0, tick_ms }; + n0.age <= n5.age + { 13'b0, tick_ms }; end end else begin - noise_age <= 0; + n0.age <= 0; // The noise LFSR is clocked after OSC bit 19 goes high, or when // reset or test is released. - if (~nclk_prev & nclk) begin + if (~n5.nclk_prev & nclk) begin // LFSR shift phase 2. // Completion of the shift is delayed by 2 cycles after OSC // bit 19 goes high. - noise <= { noise[21:0], (nres_prev | noise[22]) ^ noise[17] }; - end else if (reg_i.noise & (reg_i.pulse | reg_i.sawtooth | reg_i.triangle)) begin + n0.lfsr <= { n5.lfsr[21:0], (n5.nres_prev | n5.lfsr[22]) ^ n5.lfsr[17] }; + end else if (control_3.noise & (control_3.pulse | control_3.sawtooth | control_3.triangle)) begin // Writeback to LFSR from combined waveforms when nclk = 1. // FIXME: This should AND in actual bit values, which are - // first calculated in sid_voice.sv. For now, we assume that - // combined waveforms are used to write zeros to the LFSR. - { noise[20], noise[18], noise[14], noise[11], noise[9], noise[5], noise[2], noise[0] } <= '0; + // first calculated after waveform selection below. For now, + // we assume that combined waveforms are used to write zeros + // to the LFSR. + // { n0.lfsr[20], n0.lfsr[18], n0.lfsr[14], + // n0.lfsr[11], n0.lfsr[9], n0.lfsr[5], n0.lfsr[2], n0.lfsr[0] } <= '0; + n0.lfsr <= { n5.lfsr[22:21], 1'b0, n5.lfsr[19], 1'b0, n5.lfsr[17:15], 1'b0, n5.lfsr[13:12], + 1'b0, n5.lfsr[10], 1'b0, n5.lfsr[8:6], 1'b0, n5.lfsr[4:3], 1'b0, n5.lfsr[1], 1'b0 }; end end end + end + + // Sawtooth / triangle. + sid::reg12_t st5 = 0, st4 = 0, st3 = 0, st2 = 0, st0 = 0, st1 = 0; + logic o2_msb_i; // For ring modulation. + logic o2_tri_xor; + sid::reg12_t saw_tri_next; + sid::reg12_t saw_tri; + + always_comb begin + // The sawtooth and triangle waveforms are constructed from the + // upper 12 bits of the oscillator. When sawtooth is not selected, + // and the MSB is high, the lower 11 of these 12 bits are + // inverted. When triangle is selected, the lower 11 bits are + // shifted up to produce the final output. The MSB may be modulated + // by the preceding oscillator for ring modulation. + o2_msb_i = (cycle == 5 || cycle == 8) ? o0[23] : o3[23]; + o2_tri_xor = ~control_4.sawtooth & ((control_4.ring_mod & ~o2_msb_i) ^ o2[23]); + + // In the 8580, sawtooth / triangle is latched by phi2, and is thus + // delayed by one SID cycle. + saw_tri_next = { o2[23], o2[22:12] ^ { 11{o2_tri_xor} } }; + saw_tri = (model_4 == sid::MOS6581) ? saw_tri_next : st5; + end + + always_ff @(posedge clk) begin + if (cycle >= 5 && cycle <= 10) begin + { st5, st4, st3, st2, st1, st0 } <= { st4, st3, st2, st1, st0, saw_tri_next }; + end + end + + // Power-on initialization. + always_ff @(posedge clk) begin + if (cycle == 10 && !primed) begin + // All oscillators and noise LFSRs are initialized. + primed <= 1; + end + end + + // ------------------------------------------------------------------------ + // Waveform selectors + // ------------------------------------------------------------------------ + sid::reg4_t waveform_4, waveform_5; + + // Pre-calculated waveforms for waveform selection. + sid::reg12_t norm = 0; // Selected regular waveform + sid::reg12_t norm_next; + sid::reg8_t pst = 0; // Combined waveforms + sid::reg8_t ps__6581 = 0; + sid::reg8_t ps__8580 = 0; + sid::reg8_t p_t_6581 = 0; + sid::reg8_t p_t_8580 = 0; + sid::reg8_t _st = 0; + + // Waveform 0 value and age. + // FIXME: Yosys doesn't support multidimensional packed arrays outside + // of structs, nor arrays of structs. + typedef struct packed { + logic [5:0][11:0] value; + logic [5:0][12:0] age; + } waveform_0_t; + + // (* nowrshmsk *) + waveform_0_t waveform_0 = '0; + logic waveform_0_faded; + logic waveform_0_tick; + + // Combined waveform lookup tables. + sid::reg8_t sid_waveform_PS__6581[2048]; + sid::reg8_t sid_waveform_PS__8580[4096]; + sid::reg8_t sid_waveform_P_T_6581[2048]; + sid::reg8_t sid_waveform_P_T_8580[2048]; + + always_comb begin + // With respect to the oscillator, the waveform cycle delays are: + // * saw_tri: 0 (6581) / 1 (8580) + // * pulse: 1 + // * noise: 2 + + waveform_4 = { control_4.noise, control_4.pulse, control_4.sawtooth, control_4.triangle }; + + // All combined waveforms which include noise output zero after a few + // cycles. + unique case (waveform_4) + 'b1000: norm_next = { noise, 4'b0 }; + 'b0100: norm_next = { 12{pulse[2]} }; + 'b0010: norm_next = saw_tri; + 'b0001: norm_next = { saw_tri[10:0], 1'b0 }; + default: norm_next = 0; + endcase + end + + always_ff @(posedge clk) begin + if (cycle >= 5 && cycle <= 10) begin + // Regular waveforms. + norm <= norm_next; + + // Combined waveform candidates from BRAM and combinational logic. + pst <= sid_waveform_PST(model_4, saw_tri); + ps__6581 <= sid_waveform_PS__6581[saw_tri[10:0]]; + ps__8580 <= sid_waveform_PS__8580[saw_tri]; + p_t_6581 <= sid_waveform_P_T_6581[saw_tri[10:0]]; + p_t_8580 <= sid_waveform_P_T_8580[saw_tri[10:0]]; + _st <= sid_waveform__ST(model_4, saw_tri); + end + end + + always_comb begin + waveform_5 = { control_5.noise, control_5.pulse, control_5.sawtooth, control_5.triangle }; + + // Final waveform selection on cycle 2. + // All inputs to the combinational logic are from cycle 1. + unique case (waveform_5) + 'b0111: wav = { pst & { 8{pulse[3]} }, 4'b0 }; + 'b0110: wav = { ((model_5 == sid::MOS6581) ? ps__6581 : ps__8580) & { 8{pulse[3]} }, 4'b0 }; + 'b0101: wav = { ((model_5 == sid::MOS6581) ? p_t_6581 : p_t_8580) & { 8{pulse[3]} }, 4'b0 }; + 'b0011: wav = { _st, 4'b0 }; + 'b0000: wav = waveform_0.value[5]; + default: wav = norm; + endcase + + // Update of waveform 0 for next cycle. + waveform_0_faded = (waveform_0.age[5] == ((model_5 == sid::MOS6581) ? + WF_0_TTL_6581 : + WF_0_TTL_8580)); + waveform_0_tick = waveform_0_faded ? 0 : tick_ms; + end + + always_ff @(posedge clk) begin + if (cycle >= 6 && cycle <= 11) begin + // Update of waveform 0. + // .value[0] and .age[0] are updated below. + { waveform_0.value[5:1] } <= { waveform_0.value[4:0] }; + { waveform_0.age[5:1] } <= { waveform_0.age[4:0] }; + + if (waveform_5 == 'b0000) begin + if (waveform_0_faded) begin + waveform_0.value[0] <= 0; + end else begin + waveform_0.value[0] <= waveform_0.value[5]; + end + waveform_0.age[0] <= waveform_0.age[5] + { 12'b0, waveform_0_tick }; + end else begin + waveform_0.value[0] <= wav; + waveform_0.age[0] <= 0; + end + end + end + + // od -An -tx1 -v reSID/src/wave6581_PS_.dat | head -128 | cut -b2- > sid_waveform_PS__6581.hex + // od -An -tx1 -v reSID/src/wave8580_PS_.dat | cut -b2- > sid_waveform_PS__8580.hex + // od -An -tx1 -v reSID/src/wave6581_P_T.dat | head -128 | cut -b2- > sid_waveform_P_T_6581.hex + // od -An -tx1 -v reSID/src/wave8580_P_T.dat | head -128 | cut -b2- > sid_waveform_P_T_8580.hex + initial begin + $readmemh("sid_waveform_PS__6581.hex", sid_waveform_PS__6581); + $readmemh("sid_waveform_PS__8580.hex", sid_waveform_PS__8580); + $readmemh("sid_waveform_P_T_6581.hex", sid_waveform_P_T_6581); + $readmemh("sid_waveform_P_T_8580.hex", sid_waveform_P_T_8580); + end + +`ifdef VM_TRACE + // Latch voices for simulation. + /* verilator lint_off UNUSED */ + typedef struct packed { + sid::reg24_t osc; + sid::reg12_t saw_tri; + logic [1:0] pulse; // Duplicate bits for analog display in GTKWave + sid::reg8_t noise; + sid::reg12_t wav; + } sim_t; + + sim_t sim[6]; + + always_ff @(posedge clk) begin + if (cycle >= 5 && cycle <= 10) begin + sim[cycle - 5].osc <= o2; + sim[cycle - 5].saw_tri <= saw_tri; + sim[cycle - 5].pulse <= { 2{pulse[2]} }; + sim[cycle - 5].noise <= noise; + end - // Pulse. - if (phase[sid::PHI2_PHI1]) begin - // The pulse width comparison is done at phi2, before the oscillator - // is updated at phi1. Thus the pulse waveform is delayed by one cycle - // with respect to the oscillator. - pulse <= (osc[23:12] >= { reg_i.pw_hi[3:0], reg_i.pw_lo }) | reg_i.test; + if (cycle >= 6 && cycle <= 11) begin + sim[cycle - 6].wav <= wav; end end + /* verilator lint_on UNUSED */ +`endif endmodule