Skip to content

Instantly share code, notes, and snippets.

@bit-hack
Last active June 22, 2021 21:29
Show Gist options
  • Save bit-hack/6c4fcf26f14149dd3b9aca1878ede5b2 to your computer and use it in GitHub Desktop.
Save bit-hack/6c4fcf26f14149dd3b9aca1878ede5b2 to your computer and use it in GitHub Desktop.
An SN76489 verilog model with uart input and I2S output
1fff
196b
1430
1009
0cbd
0a1e
0809
066a
0512
0407
0333
028a
0204
019a
0146
0000
e000
e695
ebcf
eff6
f342
f5e1
f7f6
f996
faed
fbf8
fccc
fd75
fdfb
fe65
feba
0000
`default_nettype none
// moving average filter
// filter 222222Hz to ~27777Hz
module filter_t(
input CLK12,
input TICK,
input signed [15:0] IN,
output signed [15:0] OUT);
reg signed [18:0] A; // accumulator
reg signed [15:0] B[8]; // sample buffer
reg [2:0] X; // write
assign OUT = A[18:3];
initial begin
X <= 'd0;
A <= 'd0;
B[0] <= 'd0;
B[1] <= 'd0;
B[2] <= 'd0;
B[3] <= 'd0;
B[4] <= 'd0;
B[5] <= 'd0;
B[6] <= 'd0;
B[7] <= 'd0;
end
always @(posedge CLK12) begin
if (TICK) begin
// add new sample, remove old one and some DC removal
A <= ((A + IN) - B[X]) + ((A > 0) ? -1 : 1);
B[X] <= IN;
X <= X + 'd1;
end
end
endmodule
module sn76489_t(
input CLK12,
input [7:0] DATA,
input WR,
output signed [15:0] OUT);
reg B[3]; // output bit
reg [9:0] C[3]; // counter
reg [9:0] F[4]; // counter max
reg [3:0] A[4]; // attenuation
wire noise_fb = F[3][2];
wire noise_sh = F[3][1:0];
reg [6:0] noise_div;
reg [15:0] lfsr;
reg [15:0] lfsr_next = noise_fb ?
((lfsr == 0) ? 1 : { lfsr[0] ^ lfsr[3], lfsr[15:1] }) : // sms genesis gamegear
{ lfsr[0], lfsr[15:1] };
reg [6:0] COUNT; // input clock divider
// true when the tone generators should be ticked
// (12000000Hz) / (3546893Hz / 16) = 54.132 ticks
// output rate = ~222222Hz
wire TICK = (COUNT == 'd53);
// output mixer
reg signed [15:0] MIX;
// output filter #2
filter_t filter(CLK12, TICK, MIX, OUT);
// channel declared in incomming data
wire [1:0] CHAN = DATA[6:5];
// previously latched register
reg [2:0] LREG;
// volume table rom
reg signed [15:0] DAC[32];
initial $readmemh("dac.hex", DAC);
wire signed [15:0] c0_mix = DAC[{B[0], A[0]}];
wire signed [15:0] c1_mix = DAC[{B[1], A[1]}];
wire signed [15:0] c2_mix = DAC[{B[2], A[2]}];
wire signed [15:0] nz_mix = DAC[{lfsr[15], A[3]}];
initial begin
// reset channel attenuation
A[0] <= 4'hf;
A[1] <= 4'hf;
A[2] <= 4'hf;
// reset core registers
LREG <= 'd0;
MIX <= 'd0;
COUNT <= 'd0;
end
// output mixer
always @(posedge CLK12) begin
if (TICK) begin
// generate new sample
MIX <= c0_mix + c1_mix + c2_mix + nz_mix;
end
end
// tone generators
always @(posedge CLK12) begin
if (TICK) begin
if (C[0] >= F[0]) begin
C[0] <= 'd0; // reset counter
B[0] <= ~B[0]; // toggle output bit
end else begin
C[0] <= C[0] + 'd1;
end
if (C[1] >= F[1]) begin
C[1] <= 'd0; // reset counter
B[1] <= ~B[1]; // toggle output bit
end else begin
C[1] <= C[1] + 'd1;
end
if (C[2] >= F[2]) begin
C[2] <= 'd0; // reset counter
B[2] <= ~B[2]; // toggle output bit
end else begin
C[2] <= C[2] + 'd1;
end
noise_div <= noise_div + 'd1;
if ((noise_sh == 'd0 && noise_div[4:0] == 0) ||
(noise_sh == 'd1 && noise_div[5:0] == 0) ||
(noise_sh == 'd2 && noise_div[6:0] == 0) ||
(noise_sh == 'd3 && C[2] >= F[2])) begin
// update noise register
lfsr <= lfsr_next;
end
end
end
// clock divider
always @(posedge CLK12) begin
if (TICK) begin
COUNT <= 'd0;
end else begin
COUNT <= COUNT + 'd1;
end
end
// data write
always @(posedge CLK12) begin
if (WR) begin
// if initial byte
if (DATA[7] == 'b1) begin
// save the last register written to
LREG <= DATA[6:4];
// freq / attenuator select
if (DATA[4] == 'b0) begin
// frequency update (low bits)
F[CHAN] <= { F[CHAN][9:4], DATA[3:0] };
end else begin
// attenuator data
A[CHAN] <= DATA[3:0];
end
end else begin
// freq / attenuator select
if (LREG[0] == 'b0) begin
// frequency update (high bits)
F[LREG[2:1]] <= { DATA[ 5:0 ], F[LREG[2:1]][3:0] };
end else begin
// attenuator update
A[LREG[2:1]] <= DATA[3:0];
end
end
end
end
endmodule
module i2s_master_t(
input CLK12,
input [15:0] SMP,
output SCK,
output BCK,
output DIN,
output LCK);
reg [ 8:0] counter;
reg [15:0] shift;
reg out;
assign SCK = CLK12; // (sck)
assign BCK = counter[2]; // (sck / 4)
assign LCK = counter[8]; // (sck / 256)
assign DIN = shift[15]; // (sck / 4)
initial begin
counter <= 'd0;
shift <= 'd0;
end
always @(posedge CLK12) begin
// on the falling edge of BCK
if (counter[2:0] == 0) begin
if (counter[7:3] == 1) begin
// re-sample at on BCK after LRCK edge
shift <= SMP;
end else begin
// shift out data
shift <= { shift[14:0], 1'b0 };
end
end
counter <= counter + 'd1;
end
endmodule
module uart_rx(input clk,
input rx,
output reg [7:0] data,
output reg recv);
localparam CLK_RATE = 12000000;
localparam BAUD = 9600;
localparam CLK_DIV = CLK_RATE / BAUD;
reg [10:0] clk_count;
reg [3:0] count;
reg rx_;
initial begin
clk_count <= 0;
data <= 0; // idle state
count <= 0;
rx_ <= 1;
recv <= 0;
end
always @(posedge clk) begin
// if we are idle
if (count == 0) begin
// if start bit has been detected
if (rx_ == 1 && rx == 0) begin
// set clock count to 1/2 clk_div so we start samping half way
// through the data cycle
clk_count <= CLK_DIV / 2;
// 8 bits + start bit
count <= 9;
end
end else begin
// time to sample the data
if (clk_count == 0) begin
// shift the data in
data <= { rx, data[7:1] };
// reduce count by one
count <= count - 1;
// prime the next counter
clk_count <= CLK_DIV;
end else begin
clk_count <= clk_count - 1;
end
end
// update the edge detector
rx_ = rx;
end
always @(posedge clk) begin
// if we have just sampled our last bit
if (count == 1 && clk_count == 0) begin
recv = 1;
end else begin
recv = 0;
end
end
endmodule
module main(input CLK, input RX, output [7:0] PM3);
reg [7:0] data;
reg recv;
uart_rx uart(CLK, RX, data, recv);
reg [15:0] mix;
sn76489_t psg(CLK, data, recv, mix);
reg SCK;
reg LRCLK;
reg DATA;
reg BCLK;
assign PM3[0] = SCK;
assign PM3[1] = BCLK;
assign PM3[2] = DATA;
assign PM3[3] = LRCLK;
wire [15:0] VAL = { {3{mix[15]}}, mix[14:2] };
i2s_master_t i2s(CLK, VAL, SCK, BCLK, DATA, LRCLK);
endmodule
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment