Packages

DANM, short for Design Automation aNd Manipulation, is a tool written by Derek for use in synthesizable RTL design.

Current section

Files

Jump to
danm lib danm verilog_printing.ex
Raw

lib/danm/verilog_printing.ex

defmodule Danm.VerilogPrinting do
@moduledoc false
alias Danm.Entity
alias Danm.WireExpr
alias Danm.BlackBox
alias Danm.Schematic
alias Danm.Sink
alias Danm.ComboLogic
alias Danm.BundleLogic
alias Danm.ChoiceLogic
alias Danm.ConditionLogic
alias Danm.CaseLogic
alias Danm.SeqLogic
alias Danm.FiniteStateMachine
alias Danm.Assertion
defstruct dict: %{},
stack: [],
stream: nil,
refs: %{}
@doc ~S"""
generate a single verilog file that have everything
"""
def generate_full_verilog(s, in: dir) do
f = File.open!("#{dir}/#{s.name}.v", [:write, :delayed_write, :utf8])
IO.puts(f, "// This file is generated by DANM on #{DateTime.utc_now()}")
%__MODULE__{stream: f}
|> setup_key_ref(module_to_key(s), s.name)
|> print_full_verilog(s)
File.close(f)
end
defp current_design(state), do: hd(state.stack)
defp begin_print(state, ref, s) do
%{state | refs: Map.put(state.refs, ref, :ongoing), stack: [s | state.stack]}
end
defp end_print(state, ref) do
%{state | refs: Map.put(state.refs, ref, :done), stack: tl(state.stack)}
end
defp print_full_verilog(state, s) do
ref = Map.fetch!(state.dict, module_to_key(s))
case Map.fetch!(state.refs, ref) do
:done ->
state
:ongoing ->
raise "Infinite recursive design in #{s.name}"
:todo ->
state
|> begin_print(ref, s)
|> print_current_design(ref)
|> end_print(ref)
end
end
defp inlined?(s) do
case s.__struct__ do
BlackBox -> false
Schematic -> false
_ -> true
end
end
defp print_current_design(state, ref) do
s = current_design(state)
case s.__struct__ do
BlackBox ->
copy_self_verilog(state, ref)
Schematic ->
s
|> Schematic.sort_sub_modules(except: &inlined?/1)
|> Enum.reduce(print_self_verilog(state, ref), fn i_name, state ->
print_full_verilog(state, Map.fetch!(s.insts, i_name))
end)
end
end
defp copy_self_verilog(state, ref) do
b = current_design(state)
f = state.stream
if ref != b.name, do: raise("A black box cannot be uniquified, got #{ref} expect #{b.name}")
# copy everything, and add a return to make sure format is proper
IO.puts(f, File.read!(b.src))
state
end
defp unique_name_like(name, from: dict) do
cond do
Map.has_key?(dict, name) -> unique_name_like(name, from: dict, salt: 1)
true -> name
end
end
defp unique_name_like(name, from: dict, salt: s) do
salted = "#{name}_#{s}"
cond do
Map.has_key?(dict, salted) -> unique_name_like(name, from: dict, salt: s + 1)
true -> salted
end
end
defp print_self_verilog(state, ref) do
s = current_design(state)
f = state.stream
sorted_ports = BlackBox.sort_ports(s)
port_string = sorted_ports |> Enum.map(&verilog_escape/1) |> Enum.join(",\n\t")
IO.write(f, ~s"""
/**
#{Schematic.doc_string(s)}
*/
module #{ref}(
#{port_string});
""")
Enum.each(sorted_ports, fn p_name ->
{dir, width} = Map.fetch!(s.ports, p_name)
case width do
1 -> IO.puts(f, " #{dir} #{verilog_escape(p_name)};")
_ -> IO.puts(f, " #{dir} [#{width - 1}:0] #{verilog_escape(p_name)};")
end
end)
map = Schematic.wire_width_map(s)
s.wires
|> Map.keys()
|> Enum.sort(:asc)
|> Enum.each(fn w_name ->
width = Map.fetch!(map, w_name)
wire_type = wire_type(s, w_name)
case width do
1 -> IO.puts(f, " #{wire_type} #{verilog_escape(w_name)};")
_ -> IO.puts(f, " #{wire_type} [#{width - 1}:0] #{verilog_escape(w_name)};")
end
end)
map = Schematic.pin_to_wire_map(s)
state =
s
|> Schematic.sort_sub_modules()
|> Enum.reduce(state, fn i_name, state ->
case Map.fetch!(s.insts, i_name).__struct__ do
ChoiceLogic ->
print_one_choice_logic(state, i_name)
ConditionLogic ->
print_one_condition_logic(state, i_name)
CaseLogic ->
print_one_case_logic(state, i_name)
SeqLogic ->
print_one_seq_logic(state, i_name)
FiniteStateMachine ->
print_one_fsm_logic(state, i_name)
Sink ->
print_one_sink(state, i_name)
Assertion ->
print_one_assertion(state, i_name)
t when t in [ComboLogic, BundleLogic] ->
print_one_simple_logic(state, i_name)
t when t in [BlackBox, Schematic] ->
print_one_instance(state, i_name, with: map)
end
end)
IO.puts(f, "endmodule // #{ref}\n")
state
end
defp wire_type(s, w_name) do
{di, _} = Schematic.driver_of_wire(s, Map.fetch!(s.wires, w_name))
case di do
:self ->
"wire"
_ ->
case Map.fetch!(s.insts, di).__struct__ do
BlackBox -> "wire"
Schematic -> "wire"
ComboLogic -> "wire"
BundleLogic -> "wire"
_ -> "reg"
end
end
end
defp module_to_key(s) do
case s.__struct__ do
BlackBox ->
s.name
Schematic ->
{s.name, s.params}
# anything else should not hava a key
end
end
defp key_to_ref(state, k) do
case Map.get(state.dict, k) do
nil ->
name =
case k do
{name, _} -> name
name -> name
end
unique_name_like(name, from: state.refs)
ref ->
ref
end
end
defp setup_key_ref(state, key, ref) do
%{
state
| dict: Map.put(state.dict, key, ref),
refs:
case Map.get(state.refs, ref) do
nil -> Map.put(state.refs, ref, :todo)
_ -> state.refs
end
}
end
defp print_one_instance(state, i_name, with: map) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
key = module_to_key(inst)
ref = key_to_ref(state, key)
IO.write(f, ~s"""
// instance of #{Entity.type_string(inst)}
#{ref} #{verilog_escape(i_name)}(
""")
conns_str =
inst
|> BlackBox.sort_ports()
|> Enum.filter(fn p_name -> Map.has_key?(map, "#{i_name}/#{p_name}") end)
|> Enum.map(fn p_name ->
w_name = Map.fetch!(map, "#{i_name}/#{p_name}")
".#{verilog_escape(p_name)}(#{verilog_escape(w_name)})"
end)
|> Enum.join(",\n\t")
IO.puts(f, "\t#{conns_str});")
# only print defparam for black boxes. schematic has parameters passed in the generated code
if inst.__struct__ == BlackBox do
inst.params
|> Map.keys()
|> Enum.sort(:asc)
|> Enum.each(fn p_name ->
p_value = Map.fetch!(inst.params, p_name)
IO.puts(
f,
" defparam #{verilog_escape(i_name)}.#{verilog_escape(p_name)} = #{p_value};"
)
end)
IO.write(f, "\n")
end
setup_key_ref(state, key, ref)
end
defp print_one_sink(state, i_name) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
IO.puts(f, "// instance of #{Entity.type_string(inst)}")
inst.inputs
|> Map.keys()
|> Enum.sort(:asc)
|> Enum.each(fn p_name ->
IO.puts(f, "//\t sink(#{verilog_escape(p_name)});")
end)
state
end
defp print_one_simple_logic(state, i_name) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
IO.write(f, " assign ")
print_simple_logic_core(inst, i_name, "=", 0, f)
state
end
defp indent(n), do: String.duplicate("\t", div(n, 8)) <> String.duplicate(" ", rem(n, 8))
defp print_simple_logic_core(inst, i_name, assign, indent, f) do
str =
case inst.__struct__ do
ComboLogic -> verilog_string(inst.expr)
BundleLogic -> BundleLogic.expr_string(inst, &verilog_escape/1)
end
IO.puts(f, "#{indent(indent)}#{verilog_escape(i_name)} #{assign} #{str};")
end
defp print_one_choice_logic(state, i_name) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
IO.puts(f, " always @(#{sensitivity_string(inst)})")
print_choice_logic_core(inst, i_name, "=", 8, f)
state
end
defp print_choice_logic_core(inst, i_name, assign, indent, f) do
w = ChoiceLogic.cond_width(inst)
IO.puts(f, "#{indent(indent)}case (#{verilog_string(inst.condition)})")
Enum.reduce(inst.choices, 0, fn c, i ->
IO.puts(
f,
"#{indent(indent + 4)}#{w}'b#{pad_string(i, 2, w)}: #{verilog_escape(i_name)} #{assign} #{verilog_string(c)};"
)
i + 1
end)
IO.puts(f, "#{indent(indent)}endcase")
end
defp print_one_condition_logic(state, i_name) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
IO.puts(f, " always @(#{sensitivity_string(inst)})")
print_condition_logic_core(inst, i_name, "=", 8, f)
state
end
defp print_condition_logic_core(inst, i_name, assign, indent, f) do
inst.conditions
|> Enum.zip(inst.choices)
|> Enum.reduce(0, fn {co, ch}, i ->
case i do
0 ->
IO.puts(f, "#{indent(indent)}if (#{verilog_string(co)})")
_ ->
case co do
{:const, _, v} when v > 0 ->
IO.puts(f, "#{indent(indent)}else")
_ ->
IO.puts(f, "#{indent(indent)}else if (#{verilog_string(co)})")
end
end
IO.puts(
f,
"#{indent(indent + 4)}#{verilog_escape(i_name)} #{assign} #{verilog_string(ch)};"
)
i + 1
end)
end
defp print_one_case_logic(state, i_name) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
IO.puts(f, " always @(#{sensitivity_string(inst)})")
print_case_logic_core(inst, i_name, "=", 8, f)
state
end
defp print_case_logic_core(inst, i_name, assign, indent, f) do
IO.puts(f, "#{indent(indent)}case (#{verilog_string(inst.condition)})")
inst.cases
|> Enum.zip(inst.choices)
|> Enum.reduce(0, fn {co, ch}, i ->
IO.puts(
f,
"#{indent(indent + 4)}#{verilog_string(co)}}: #{verilog_escape(i_name)} #{assign} #{verilog_string(ch)};"
)
i + 1
end)
IO.puts(f, "#{indent(indent)}endcase")
end
defp print_one_seq_logic(state, i_name) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
IO.puts(f, " always @(posedge #{verilog_escape(inst.clk)})")
case inst.core.__struct__ do
ChoiceLogic ->
print_choice_logic_core(inst.core, i_name, "<=", 8, f)
ConditionLogic ->
print_condition_logic_core(inst.core, i_name, "<=", 8, f)
CaseLogic ->
print_case_logic_core(inst.core, i_name, "<=", 8, f)
_ ->
print_simple_logic_core(inst.core, i_name, "<=", 8, f)
end
state
end
defp print_one_fsm_logic(state, i_name) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
IO.puts(f, " always @(posedge #{verilog_escape(inst.clk)})")
IO.puts(f, "\tcase (#{verilog_escape(i_name)})")
Enum.each(inst.graph, fn {state, transit} ->
IO.puts(f, "\t #{inst.width}'d#{state}:")
Enum.reduce(transit, 0, fn {co, ns}, i ->
case i do
0 ->
IO.puts(f, "\t\tif (#{verilog_string(co)})")
_ ->
case co do
{:const, _, v} when v > 0 -> IO.puts(f, "\t\telse")
_ -> IO.puts(f, "\t\telse if (#{verilog_string(co)})")
end
end
IO.puts(f, "\t\t #{verilog_escape(i_name)} <= #{inst.width}'d#{ns};")
i + 1
end)
end)
IO.puts(f, "\t default: #{verilog_escape(i_name)} <= #{inst.width}'d0;")
IO.puts(f, "\tendcase")
state
end
defp print_one_assertion(state, i_name) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
case inst.clk do
nil -> IO.puts(f, " always @(#{sensitivity_string(inst)})")
clk -> IO.puts(f, " always @(posedge #{verilog_escape(clk)})")
end
IO.write(f, ~s"""
\tif (($stime > #{inst.silent_time})&&#{verilog_string(inst.expr)}) begin
\t $fdisplay(32'h80000002, "Assertion #{s.name}.#{i_name} failed at %%d", $stime);
\t $finish;
\tend
""")
state
end
defp pad_string(n, b, w) do
str = Integer.to_string(n, b)
String.duplicate("0", w - String.length(str)) <> str
end
defp sensitivity_string(inst) do
inst.inputs |> Map.keys() |> Enum.map(&verilog_escape/1) |> Enum.join(" or ")
end
defp verilog_string(term), do: WireExpr.ast_string(term, &verilog_escape/1)
defp verilog_escape(str) do
cond do
String.contains?(str, "/") -> "\\" <> str <> " "
true -> str
end
end
end