Last active
June 22, 2021 21:29
-
-
Save bit-hack/6c4fcf26f14149dd3b9aca1878ede5b2 to your computer and use it in GitHub Desktop.
An SN76489 verilog model with uart input and I2S output
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
`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