Skip to content

Instantly share code, notes, and snippets.

@bit-hack
Last active December 3, 2019 16:31
Show Gist options
  • Save bit-hack/b395b35eabe6de7efc6a886f4fb7dfc0 to your computer and use it in GitHub Desktop.
Save bit-hack/b395b35eabe6de7efc6a886f4fb7dfc0 to your computer and use it in GitHub Desktop.
IceStick SN76489 I2S VGM player
`default_nettype none
`timescale 1ns / 1ps
module i2s_master(input CLK,
input [15:0] LIN,
input [15:0] RIN,
output LRCLK,
output DATA,
output BCLK);
// @48Mhz CLK
// 48000000 / (44100 * 32) = 34.01
// 6 - bits
reg [6:0] up_cnt;
reg [5:0] count;
reg [31:0] sr;
initial begin
up_cnt <= 0;
count <= 0;
sr <= 0;
end
assign BCLK = (up_cnt > 8) ? 1 : 0;
always @(negedge CLK) begin
if (up_cnt == 17) begin
up_cnt <= 0;
count <= count + 5'd1;
if (count[4:0] == 5'd0) begin
if (count[5] == 1'b0) begin
// prep left data
sr <= { 1'b0, LIN[15:1], 16'b0 };
end else begin
// prep right data
sr <= { 1'b0, RIN[15:1], 16'b0 };
end
end else begin
// shift all left
sr <= { sr[30:0], 1'b0 };
end
end else begin
up_cnt <= up_cnt + 1;
end
end
assign LRCLK = count[5];
assign DATA = sr[31];
endmodule
module sn76489(input CLK,
input WR,
input [7:0] DATA,
output [15:0] OUT,
input RST);
reg [7:0] ldata;
// latched oscillator address
reg [2:0] addr;
// pitch data
// f = N / 32n
// where N = ref clock in Hz
// n = 10bit register
reg [9:0] pitch[4];
// volume data
// 0001 2db
// 0010 4db
// 0100 8db
// 1000 16db
// 1111 0ff
reg [3:0] vol[4];
// tone register
reg [9:0] tone[4];
reg [15:0] lfsr;
// pitch[3] is the Noise Control
// .......fNN
// were f = FB
// N = NF0,NF1
// pre-attenuation output bits:
// { lfsr, tone2, tone1, tone0 }
reg [2:0] pre_atten;
// volume table ROM
reg [15:0] volume_table[32];
initial $readmemh("volume_table.txt", volume_table);
// mixer output
reg [15:0] out;
// channel pre-mixer
reg [15:0] chan0;
reg [15:0] chan1;
reg [15:0] chan2;
reg [15:0] chan3;
// noise clock divider
reg [6:0] noise_clk;
/*
// at start of simulation
initial begin
integer i=0;
for (i=0; i<4; i=i+1) begin
pitch[i] <= 10'h0;
vol[i] <= 10'h0;
tone[i] <= 10'h0;
end
lfsr <= 16'h0001;
pre_atten <= 3'h0;
noise_clk <= 7'b0;
end
*/
// always assign output
assign OUT = out;
// update noise
always @(posedge noise_clk[3]) begin
// TODO: This needs needs to happen at /16 rate
// Then optional 2/4/8 divide
lfsr <= (lfsr == 0) ? 1 : { lfsr[0] ^ lfsr[3], lfsr[15:1] };
end
// on data write
always @(negedge WR) begin
ldata <= DATA;
end
// on rising edge of clock
always @(posedge CLK) begin
// update lfsr
noise_clk <= noise_clk + 7'd1;
// for all three voices
if (tone[0] == 10'b0) begin
// reset period
tone[0] <= pitch[0];
pre_atten[0] = ~pre_atten[0];
end else begin
// decrement
tone[0] <= tone[0] - 10'd1;
end
if (tone[1] == 10'b0) begin
// reset period
tone[1] <= pitch[1];
pre_atten[1] = ~pre_atten[1];
end else begin
// decrement
tone[1] <= tone[1] - 10'd1;
end
if (tone[2] == 10'b0) begin
// reset period
tone[2] <= pitch[2];
pre_atten[2] = ~pre_atten[2];
end else begin
// decrement
tone[2] <= tone[2] - 10'd1;
end
// channel attenuation
chan0 = volume_table[{pre_atten[0], vol[0]}];
chan1 = volume_table[{pre_atten[1], vol[1]}];
chan2 = volume_table[{pre_atten[2], vol[2]}];
chan3 = volume_table[{lfsr[0], vol[3]}];
// output mixer
out <= chan0 + chan1 + chan2 + chan3 + 16'h8000;
end
always @(negedge CLK or posedge RST) begin
if (RST) begin
vol[0] <= 4'hf;
vol[1] <= 4'hf;
vol[2] <= 4'hf;
vol[3] <= 4'hf;
end else begin
// latch R0, R1, R2
addr = ldata[7] ? ldata[6:4] : addr;
// if latch data
if (ldata[7]) begin
// R2 = 0:tone, 1:vol
if (ldata[4] == 1'b1 /* R2 */) begin
// ......xxxx
vol[ldata[6:5]] <= ldata[3:0];
end else begin
// ......xxxx
pitch[ldata[6:5]][3:0] <= ldata[3:0];
end
// non latch data
end else begin
// R2 = 0:tone, 1:vol
if (addr[0] == 1'b1 /* R2 */) begin
// ......xxxx
vol[addr[2:1]] <= ldata[3:0];
end else begin
// xxxxxx....
pitch[addr[2:1]][9:4] <= ldata[5:0];
end
end
end
end
endmodule
module uart_tx(
input clk, // Master clock
input rst, // Synchronous reset
input [7:0] tx_byte, // Byte to transmit
input start, // Start
output tx // transmit pin
);
parameter baud_rate = 9600;
parameter sys_clk_freq = 48000000;
// receive counts
localparam full_cycle = sys_clk_freq / baud_rate;
localparam half_cycle = full_cycle / 2;
// receive clock
localparam clk_bits = $clog2(full_cycle);
reg [clk_bits-1:0] rx_clk;
// state machine
localparam [1:0]
STATE_IDLE = 1'd0,
STATE_SEND = 1'd1;
reg state;
reg bit_tx;
reg [3:0] bit_cnt;
reg [9:0] bit_shift;
reg old_start;
reg pending_start;
assign tx = bit_tx;
/*
initial begin
state <= STATE_IDLE;
bit_tx <= 0;
bit_shift <= 0;
rx_clk <= 0;
bit_cnt <= 0;
pending_start <= 0;
end
*/
always @(posedge clk) begin
if (rst) begin
state <= STATE_IDLE;
bit_tx <= 0;
bit_shift <= 0;
rx_clk <= 0;
bit_cnt <= 0;
pending_start <= 0;
end else begin
if (old_start == 1'b0 && start == 1'b1 && pending_start == 0) begin
pending_start <= 1'b1;
end
old_start <= start;
case (state)
STATE_IDLE: begin
bit_cnt <= 0;
bit_tx <= pending_start ? 1'b0 : 1'b1;
if (pending_start == 1'b1) begin
pending_start <= 1'b0;
state <= STATE_SEND;
rx_clk <= 0;
// prepare shift register
// start byte end
bit_shift <= { 1'b1, tx_byte, 1'b0 };
end
end
STATE_SEND: begin
if (rx_clk == full_cycle) begin
if (bit_cnt == 9) begin
state <= STATE_IDLE;
end else begin
rx_clk <= 0;
bit_cnt <= bit_cnt + 1;
bit_shift <= { 1'b0, bit_shift[9:1] };
end
end else begin
if (rx_clk == 0) begin
// send MSB
bit_tx <= bit_shift[0];
end
rx_clk <= rx_clk + 1;
end
end
endcase
end
end
endmodule
module uart_rx(
input clk, // Master clock
input rst, // Synchronous reset
input rx, // Incoming serial line
output received, // Indicates that a byte has been received
output [7:0] rx_byte // Byte received
);
parameter baud_rate = 9600;
parameter sys_clk_freq = 48000000;
// receive counts
localparam full_cycle = sys_clk_freq / baud_rate;
localparam half_cycle = full_cycle / 2;
localparam quat_cycle = full_cycle / 4;
// state machine
localparam [1:0]
STATE_IDLE = 2'd0,
STATE_RECV = 2'd1,
STATE_STOP = 2'd2;
reg [1:0] state;
// receive clock
localparam clk_bits = $clog2(full_cycle);
reg [clk_bits-1:0] rx_clk;
// bytes shifted in
reg [3:0] rx_count;
// received byte
reg [7:0] rx_out;
// temporary shift register
reg [7:0] rx_shift;
reg [clk_bits-1:0] rx_filter;
assign rx_byte = rx_out;
assign received = (state == STATE_STOP);
/*
initial begin
state <= STATE_IDLE;
rx_clk <= 0;
rx_count <= 0;
rx_out <= 8'd0;
rx_shift <= 8'd0;
rx_filter <= 0;
end
*/
always @(posedge clk) begin
if (rst) begin
state <= STATE_IDLE;
rx_clk <= 0;
rx_count <= 0;
rx_out <= 8'd0;
rx_shift <= 8'd0;
rx_filter <= 0;
end else begin
case (state)
// in an idle state
// wait for rx to drop low for a half cycle so we can be confident we have
// a start bit. wait in idle state for a full cycle before proceeding so
// we have a full start bit.
STATE_IDLE: begin
rx_count <= 0;
rx_filter <= 0;
if (rx == 0 || rx_clk > half_cycle) begin
if (rx_clk == full_cycle) begin
state <= STATE_RECV;
rx_clk <= 0;
end else begin
rx_clk <= rx_clk + 1;
end
end else begin
// false start reset
// this might be too harsh and could be filtered
rx_clk <= 0;
end
end
// receive 8 bits that make up our byte. sample the data on the half cycle
// so the signal is stable. we use an average filter to smooth and errors.
STATE_RECV: begin
if (rx_clk == half_cycle) begin
// clock in a new byte
// 1 or 0 depending on which it was more often during the half cycle
rx_shift <= { ( rx_filter > quat_cycle ? 1'b1 : 1'b0), rx_shift[7:1] };
rx_count <= rx_count + 1;
rx_clk <= rx_clk + 1;
end else begin
if (rx_clk == full_cycle) begin
rx_clk <= 0;
rx_filter <= 0;
if (rx_count == 8) begin
rx_out <= rx_shift;
state <= STATE_STOP;
end
end else begin
rx_clk <= rx_clk + 1;
rx_filter <= rx_filter + rx;
end
end
end
// parse the stop bit where rx goes high for one cycle.
STATE_STOP: begin
if (rx_clk == full_cycle || (rx_clk > quat_cycle && rx == 0)) begin
state <= STATE_IDLE;
rx_clk <= 0;
end else begin
rx_clk <= rx_clk + 1;
end
end
// we should never get here
default: begin
state <= STATE_IDLE;
end
endcase
end
end
endmodule
// SDIN PMOD1
// SCLK PMOD2
// LRCLK PMOD3
// MCLK PMOD4
// GND /
// VCC /
module vgm(input CLK, input RX, output PMOD1, output PMOD2, output PMOD3);
wire I2S_LRCLK;
wire I2S_DATA;
wire I2S_BCLK;
wire pll_clk_out;
wire BYPASS;
wire RESETB;
wire LOCKED;
wire global_clk;
wire rst = ~LOCKED;
assign BYPASS = 0;
assign RESETB = 1;
assign PMOD1 = I2S_DATA;
assign PMOD2 = I2S_BCLK;
assign PMOD3 = I2S_LRCLK;
// PLLOUT = (CLK_IN * (DIVF + 1)) / (2^DIVQ * (DIVR + 1))
// create a 48Mhz clock
SB_PLL40_CORE #(
.FEEDBACK_PATH("SIMPLE"),
.PLLOUT_SELECT("GENCLK"),
.DIVR(4'b0000),
.DIVF(7'b0011111),
.DIVQ(3'b011),
.FILTER_RANGE(3'b001)
) uut (
.REFERENCECLK (CLK),
.PLLOUTGLOBAL (pll_clk_out), // output frequency
.BYPASS (BYPASS),
.RESETB (RESETB), // active low
.LOCK (LOCKED) // active high
);
// create a global clock buffer
SB_GB gclkbuf(.USER_SIGNAL_TO_GLOBAL_BUFFER(pll_clk_out),
.GLOBAL_BUFFER_OUTPUT(global_clk));
wire [7:0] data;
wire recv;
uart_rx #(
.baud_rate(9600),
.sys_clk_freq(48000000)
)
my_rx(
global_clk, // The master clock for this module
1'b0, // Synchronous reset
RX, // Incoming serial line
recv, // Indicates that a byte has been received
data // Byte received
);
// generate SN76489 clock
reg [7:0] sn_cnt;
wire sn_clk = (sn_cnt > 96) ? 1 : 0;
always @(posedge global_clk) begin
if (sn_cnt == 192) begin
sn_cnt <= 0;
end else begin
sn_cnt <= sn_cnt + 1;
end
end
wire [15:0] tone;
sn76489 chip(sn_clk, recv, data, tone, 1'b0);
// create I2S master
i2s_master i2s(global_clk, tone, tone, I2S_LRCLK, I2S_DATA, I2S_BCLK);
endmodule
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment