Skip to content

Instantly share code, notes, and snippets.

@khswong
Created December 26, 2020 02:38
Show Gist options
  • Save khswong/e96d1d383c2b3003f2f67b90aad3a2ed to your computer and use it in GitHub Desktop.
Save khswong/e96d1d383c2b3003f2f67b90aad3a2ed to your computer and use it in GitHub Desktop.
its just a guy
from nmigen import *
class PWM(Elaboratable):
def __init__(self):
self.i = Signal(8)
self.o = Signal()
def elaborate(self, platform) -> Module:
m = Module()
counter = Signal(8)
m.d.comb += self.o.eq(counter < self.i)
m.d.sync += counter.eq(counter + 1)
return m
class LEDBlinker(Elaboratable):
def __init__(self, width=16, gamma=2.2):
self.width = width
self.gamma = gamma
self.pdm_g = PDMDriver()
self.pdm_r = PDMDriver()
self.cnt = PDMCounter(gamma=gamma)
def elaborate(self, platform):
ledr_n = platform.request("led", 0)
ledg_n = platform.request("led", 1)
ledr1_n = platform.request("led", 2)
ledg1_n = platform.request("led", 3)
ledr2_n = platform.request("led", 4)
ledg2_n = platform.request("led", 5)
ledr3_n = platform.request("led", 6)
ledg3_n = platform.request("led", 7)
m = Module()
m.submodules.pdm_g = self.pdm_g
m.submodules.pdm_r = self.pdm_r
m.submodules.cnt = self.cnt
m.d.comb += [
self.pdm_g.pdm_in.eq(self.cnt.pdm_level1),
self.pdm_r.pdm_in.eq(self.cnt.pdm_level2),
ledg_n.eq(self.pdm_g.pdm_out),
ledr_n.eq(self.pdm_r.pdm_out),
ledg1_n.eq(self.pdm_g.pdm_out),
ledr1_n.eq(self.pdm_r.pdm_out),
ledg2_n.eq(self.pdm_g.pdm_out),
ledr2_n.eq(self.pdm_r.pdm_out),
ledg3_n.eq(self.pdm_g.pdm_out),
ledr3_n.eq(self.pdm_r.pdm_out),
]
return m
# PDM generator
#
# Pulse Density Modulation for controlling LED intensity.
# The theory is as follows:
# given a desired target level 0 <= T <= 1, control the output pdm_out
# in {1,0}, such that pdm_out on average is T. Do this by integrating the
# error T - pdm_out over time and switch pdm_out such that the sum of
# (T - pdm_out) is finite.
#
# pdm_sigma = 0, pdm_out = 0
# forever
# pdm_sigma = pdm_sigma + (T - pdm_out)
# if (pdm_sigma >= 0)
# pdm_out = 1
# else
# pdm_out = 0
#
# Check: T = 0, pdm_out is never turned on; T = 1, pdm_out is olways on;
# T = 0.5, pdm_out toggles
#
# In fixed point arithmetic this becomes the following (assume N-bit arith)
# pdm_sigma = pdm_sigma_float * 2^N = pdm_sigma_float << N.
# As |pdm_sigma| <= 1, N+2 bits is sufficient
#
# pdm_sigma = 0, pdm_out = 0
# forever
# D = T + (~pdm_out + 1) << N === T + (pdm_out << N) + (pdm_out << (N+1))
# pdm_sigma = pdm_sigma + D
# pdm_out = 1 & (pdm_sigma >> (N+1))
class PDMDriver(Elaboratable):
def __init__(self, in_width=16):
self.pdm_out = Signal(1)
self.pdm_in = Signal(in_width)
self.in_width = in_width
def elaborate(self, _platform):
m = Module()
pdm_sigma = Signal(self.in_width + 2)
m.d.comb += self.pdm_out.eq(~pdm_sigma[-1])
m.d.sync += [
pdm_sigma.eq(pdm_sigma + Cat(self.pdm_in, self.pdm_out, self.pdm_out))
]
return m
class PDMCounter(Elaboratable):
def __init__(self, in_width=8, out_width=16, gamma=2.2):
# Somewhat matter of preference whether to put submodules/Memory in
# __init__() or elaborate, esp if submodule depends on other parameters
# sent to __init__(). Contrast to Blinker, where Signals get maxperiod
# in elaborate from self.maxperiod; there is no "self.gamma" here.
gamma_init = [int(pow(1 / 255.0 * i, gamma) * 0xFFFF)
for i in range(256)]
self.gamma_table = Memory(width=out_width, depth=2**in_width, init=gamma_init)
self.in_width = in_width
self.out_width = out_width
self.pdm_level1 = Signal(out_width)
self.pdm_level2 = Signal.like(self.pdm_level1)
def elaborate(self, _platform) -> Module:
m = Module()
m.submodules.gamma_rd_p = gamma_rd_p = self.gamma_table.read_port()
m.submodules.gamma_rd_n = gamma_rd_n = self.gamma_table.read_port()
pdm_level_gamma_p = Signal.like(self.pdm_level2)
pdm_level_gamma_n = Signal.like(pdm_level_gamma_p)
pdm_count = Signal(self.out_width + 1)
pdm_level = Signal(self.in_width + 1)
m.d.sync += [
pdm_count.eq(pdm_count + 1)
]
with m.If(pdm_count[-1] == 1):
m.d.sync += [
pdm_count.eq(0),
pdm_level.eq(pdm_level + 1)
]
# In the Verilog version, the output data from the gamma table is
# in an explicit always/sync block. The default memory in nmigen has
# a synchronous read port (asynchronous=False), so data appears one
# clock cycle after addr is put on the bus.
# Therefore we connect nets directly.
m.d.comb += [
gamma_rd_p.addr.eq(pdm_level),
gamma_rd_n.addr.eq(~pdm_level),
pdm_level_gamma_p.eq(gamma_rd_p.data),
pdm_level_gamma_n.eq(gamma_rd_n.data)
]
with m.If(pdm_level[-1]):
m.d.comb += [
self.pdm_level1.eq(pdm_level_gamma_p),
self.pdm_level2.eq(pdm_level_gamma_n)
]
with m.Else():
m.d.comb += [
self.pdm_level1.eq(pdm_level_gamma_n),
self.pdm_level2.eq(pdm_level_gamma_p)
]
return m
from nmigen_boards.ulx3s import *
if __name__ == '__main__':
platform = ULX3S_12F_Platform()
top = LEDBlinker()
platform.build(top, do_program=True)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment