logicuit 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0dad7e6a6e7b924c46183e5ef4bfc6e4647fee1d8bc64b10792569664a6c4b80
4
- data.tar.gz: a5c316448fc8b3019488131b53ad8115e11da5151fddc6f1d40d821f2b9862b8
3
+ metadata.gz: 8c670278d5f0547d3b3acdaa0f828731de1351c1d4148c2df2b57bae5a43a199
4
+ data.tar.gz: f84a271fd5df3767035f77ba196a84638a5214d52b71cd7151c81b18162709a5
5
5
  SHA512:
6
- metadata.gz: e5b4c0ea5bf5922c99a6ed7ee0d1a0eafe388c481bd1c54995645f01498adb9979c8967e1f70bcbbc7327b9c6ed2b43bd06fef326b25b73bd24beca3705a0fcc
7
- data.tar.gz: f37bec9789e8292558fd8cf58d4aa29a1765ba6d82826315afe1f4d11a24d2bb99d92ba88bdd52f52ff0e74c160ca552ebc99067f7c20151c127f7f756eee593
6
+ metadata.gz: 95e3514d3a0327f43becf3066a5a9ad6ecf4dcc51af6af0dfcbf1475fe055faaa715925878422fdc268c261006ea66514b7f6ad90a721c5f0f193cd66104231a
7
+ data.tar.gz: 39f1237f88d0bcf06d8aed9b08d9e9fd0c7b6f9659c8b6843c8c320382aff5a249f82c2e180a508fb3e9dac14f7eef04d02f6ed2037d14481d6abf2b8fdd3cc6
data/.rubocop.yml CHANGED
@@ -1,8 +1,13 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 3.1
3
+ NewCops: disable
3
4
 
4
5
  Style/StringLiterals:
5
6
  EnforcedStyle: double_quotes
6
7
 
7
8
  Style/StringLiteralsInInterpolation:
8
9
  EnforcedStyle: double_quotes
10
+
11
+ require:
12
+ - rubocop-minitest
13
+ - rubocop-rake
data/README.md CHANGED
@@ -18,14 +18,52 @@ gem install logicuit
18
18
 
19
19
  ## Usage
20
20
 
21
+ This is the code to create a 1-bit CPU:
22
+
21
23
  ```
22
- Logicuit::And.new(1, 1).signal # [1]
23
- Logicuit::And.new(1, 0).signal # [0]
24
- Logicuit::And.new(0, 1).signal # [0]
25
- Logicuit::And.new(0, 0).signal # [0]
24
+ require "logicuit"
25
+
26
+ # 1 bit CPU
27
+ #
28
+ # +-(Y)-|NOT|-(A)-+
29
+ # | |
30
+ # +-(D)-| |-(Q)-+
31
+ # |DFF|
32
+ # (CK)-|> |
33
+ #
34
+ class OneBitCpu
35
+ def initialize
36
+ @dff = Logicuit::Circuits::Sequential::DFlipFlop.new
37
+ @not = Logicuit::Gates::Not.new
38
+ @dff.q >> @not.a
39
+ @not.y >> @dff.d
40
+ end
41
+
42
+ def to_s
43
+ <<~CIRCUIT
44
+ +-(#{@not.y})-|NOT|-(#{@not.a})-+
45
+ | |
46
+ +-(#{@dff.d})-| |-(#{@dff.q})-+
47
+ |DFF|
48
+ (CK)-|> |
49
+ CIRCUIT
50
+ end
51
+ end
52
+
53
+ obc = OneBitCpu.new
54
+ loop do
55
+ system("clear")
56
+ puts obc
57
+ sleep 1
58
+ Logicuit::Signals::Clock.tick
59
+ end
26
60
  ```
27
61
 
28
- This is all for now :)
62
+ you can execute the following as a one-liner:
63
+
64
+ ```
65
+ $ ruby -r logicuit -e 'Logicuit::Circuits::SystemLevel::OneBitCpu.run'
66
+ ```
29
67
 
30
68
  ## Development
31
69
 
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logicuit
4
+ # base class for all gates and circuits
5
+ class Base
6
+ def initialize(*args)
7
+ @input_targets = []
8
+ @output_targets = []
9
+ define_inputs(*args)
10
+ define_outputs
11
+ evaluate
12
+ end
13
+
14
+ attr_reader :input_targets, :output_targets
15
+
16
+ def self.define_inputs(*inputs) # rubocop:disable Metrics/MethodLength
17
+ inputs.each do |input|
18
+ define_method(input) do
19
+ instance_variable_get("@#{input}")
20
+ end
21
+ end
22
+
23
+ define_method(:define_inputs) do |*args|
24
+ inputs.each_with_index do |input, index|
25
+ signal = Signals::Signal.new(args[index] == 1)
26
+ signal.on_change << self
27
+ instance_variable_set("@#{input}", signal)
28
+ @input_targets << input
29
+ end
30
+ end
31
+ end
32
+
33
+ def self.define_outputs(**outputs) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
34
+ outputs.each_key do |output|
35
+ define_method(output) do
36
+ instance_variable_get("@#{output}")
37
+ end
38
+ end
39
+
40
+ define_method(:define_outputs) do
41
+ outputs.each_key do |output|
42
+ instance_variable_set("@#{output}", Signals::Signal.new(false))
43
+ @output_targets << output
44
+ end
45
+ end
46
+
47
+ define_method(:evaluate) do
48
+ outputs.each do |output, evaluator|
49
+ signal = instance_variable_get("@#{output}")
50
+ if evaluator.call(*@input_targets.map do |input|
51
+ instance_variable_get("@#{input}").current
52
+ end)
53
+ signal.on
54
+ else
55
+ signal.off
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ def self.diagram(source)
62
+ define_method(:to_s) do
63
+ (@input_targets + @output_targets).reduce(source) do |result, input|
64
+ result.gsub(/\(#{input}\)/i, "(#{instance_variable_get("@#{input}")})#{"-" * (input.size - 1)}")
65
+ end
66
+ end
67
+ end
68
+
69
+ def self.truth_table(source) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
70
+ define_method(:truth_table) do # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity,Metrics/BlockLength
71
+ rows = source.strip.split("\n")
72
+ headers = rows.shift.split("|").map(&:strip).reject(&:empty?).map(&:downcase).map(&:to_sym)
73
+ rows.shift # devide line
74
+ table = rows.map do |row|
75
+ row.split("|").map(&:strip).reject(&:empty?).map(&:downcase).map do |v|
76
+ case v
77
+ when "x"
78
+ :any
79
+ when "1"
80
+ true
81
+ when "0"
82
+ false
83
+ else
84
+ raise "Invalid value in truth table: #{v}"
85
+ end
86
+ end
87
+ end.select do |values| # rubocop:disable Style/MultilineBlockChain
88
+ headers.size == values.size
89
+ end.map do |values| # rubocop:disable Style/MultilineBlockChain
90
+ array = [values]
91
+ while array.any? { |values| values.any? { |v| v == :any } }
92
+ target_index = array.find_index { |values| values.any? { |v| v == :any } }
93
+ target = array[target_index]
94
+ prop_index = target.find_index { |v| v == :any }
95
+ array.delete_at(target_index)
96
+ array.insert(target_index, *[true, false].map do |v|
97
+ target.dup.tap do |a|
98
+ a[prop_index] = v
99
+ end
100
+ end)
101
+ end
102
+ array
103
+ end.flatten!(1).map do |values| # rubocop:disable Style/MultilineBlockChain
104
+ headers.zip(values).to_h
105
+ end
106
+ table
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TODO: implement the HalfAdder class
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logicuit
4
+ module Circuits
5
+ module Combinational
6
+ # A Multiplexer with 2 inputs and 1 output
7
+ class Multiplexer2To1 < Base
8
+ define_inputs :c0, :c1, :a
9
+
10
+ define_outputs y: lambda { |c0, c1, a|
11
+ (c0 && !a) || (c1 && a)
12
+ }
13
+
14
+ diagram <<~DIAGRAM
15
+ (C0)---------|
16
+ |AND|--+
17
+ +-|NOT|-| +--|
18
+ | |OR|--(Y)
19
+ (C1)---------| +--|
20
+ | |AND|--+
21
+ (A)--+-------|
22
+ DIAGRAM
23
+
24
+ truth_table <<~TRUTH_TABLE
25
+ | C0 | C1 | A | Y |
26
+ | -- | -- | - | - |
27
+ | 0 | x | 0 | 0 |
28
+ | 1 | x | 0 | 1 |
29
+ | x | 0 | 1 | 0 |
30
+ | x | 1 | 1 | 1 |
31
+ TRUTH_TABLE
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logicuit
4
+ module Circuits
5
+ module Sequential
6
+ # D Flip-Flop
7
+ #
8
+ # (D)--| |--(Q)
9
+ # |DFF|
10
+ # (CK)-|> |
11
+ #
12
+ class DFlipFlop
13
+ def initialize(d = 0) # rubocop:disable Naming/MethodParameterName
14
+ Signals::Clock.on_tick << self
15
+
16
+ @d = d.is_a?(Signals::Signal) ? d : Signals::Signal.new(d == 1)
17
+
18
+ @q = Signals::Signal.new(false)
19
+ evaluate
20
+ end
21
+
22
+ attr_reader :d, :q
23
+
24
+ def evaluate
25
+ if d.current
26
+ q.on
27
+ else
28
+ q.off
29
+ end
30
+ end
31
+
32
+ def to_s
33
+ <<~CIRCUIT
34
+ (#{d})--| |--(#{q})
35
+ |DFF|
36
+ (CK)-|> |
37
+ CIRCUIT
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logicuit
4
+ module Circuits
5
+ module SystemLevel
6
+ # 1 bit CPU
7
+ #
8
+ # +-(Y)-|NOT|-(A)-+
9
+ # | |
10
+ # +-(D)-| |-(Q)-+
11
+ # |DFF|
12
+ # (CK)-|> |
13
+ #
14
+ class OneBitCpu
15
+ def initialize
16
+ @dff = Sequential::DFlipFlop.new
17
+ @not = Gates::Not.new
18
+ @dff.q >> @not.a
19
+ @not.y >> @dff.d
20
+ end
21
+
22
+ def to_s
23
+ <<~CIRCUIT
24
+ +-(#{@not.y})-|NOT|-(#{@not.a})-+
25
+ | |
26
+ +-(#{@dff.d})-| |-(#{@dff.q})-+
27
+ |DFF|
28
+ (CK)-|> |
29
+ CIRCUIT
30
+ end
31
+
32
+ def self.run
33
+ obc = new
34
+ loop do
35
+ system("clear")
36
+ puts obc
37
+ sleep 1
38
+ Signals::Clock.tick
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logicuit
4
+ module Gates
5
+ # AND gate
6
+ class And < Base
7
+ define_inputs :a, :b
8
+
9
+ define_outputs y: ->(a, b) { a && b }
10
+
11
+ diagram <<~DIAGRAM
12
+ (A)-|
13
+ |AND|-(Y)
14
+ (B)-|
15
+ DIAGRAM
16
+
17
+ truth_table <<~TRUTH_TABLE
18
+ | A | B | Y |
19
+ | - | - | - |
20
+ | 0 | 0 | 0 |
21
+ | 1 | 0 | 0 |
22
+ | 0 | 1 | 0 |
23
+ | 1 | 1 | 1 |
24
+ TRUTH_TABLE
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logicuit
4
+ module Gates
5
+ # NOT gate
6
+ class Not < Base
7
+ define_inputs :a
8
+
9
+ define_outputs y: ->(a) { !a } # rubocop:disable Style/SymbolProc
10
+
11
+ diagram <<~DIAGRAM
12
+ (A)-|NOT|-(Y)
13
+ DIAGRAM
14
+
15
+ truth_table <<~TRUTH_TABLE
16
+ | A | Y |
17
+ | - | - |
18
+ | 0 | 1 |
19
+ | 1 | 0 |
20
+ TRUTH_TABLE
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logicuit
4
+ module Gates
5
+ # OR gate
6
+ class Or < Base
7
+ define_inputs :a, :b
8
+
9
+ define_outputs y: ->(a, b) { a || b }
10
+
11
+ diagram <<~DIAGRAM
12
+ (A)-|
13
+ |OR|-(Y)
14
+ (B)-|
15
+ DIAGRAM
16
+
17
+ truth_table <<~TRUTH_TABLE
18
+ | A | B | Y |
19
+ | - | - | - |
20
+ | 0 | 0 | 0 |
21
+ | 1 | 0 | 1 |
22
+ | 0 | 1 | 1 |
23
+ | 1 | 1 | 1 |
24
+ TRUTH_TABLE
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logicuit
4
+ module Signals
5
+ # Clock
6
+ class Clock
7
+ def initialize
8
+ @on_tick = []
9
+ end
10
+
11
+ attr_reader :on_tick
12
+
13
+ def tick
14
+ @on_tick.each(&:evaluate)
15
+ end
16
+
17
+ def self.instance
18
+ @instance ||= new
19
+ end
20
+
21
+ def self.on_tick
22
+ instance.on_tick
23
+ end
24
+
25
+ def self.tick
26
+ instance.tick
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logicuit
4
+ module Signals
5
+ # Signal
6
+ class Signal
7
+ def initialize(current = false) # rubocop:disable Style/OptionalBooleanParameter
8
+ @current = current
9
+ @connected_by = nil
10
+ @on_change = []
11
+ end
12
+
13
+ attr_reader :current, :on_change
14
+ attr_accessor :connected_by
15
+
16
+ def on
17
+ changed = @current.!
18
+ @current = true
19
+ @on_change.each(&:evaluate) if changed
20
+ end
21
+
22
+ def off
23
+ changed = @current
24
+ @current = false
25
+ @on_change.each(&:evaluate) if changed
26
+ end
27
+
28
+ def toggle
29
+ @current ? off : on
30
+ end
31
+
32
+ def connects_to(other)
33
+ other.connected_by = self
34
+ other.evaluate
35
+ on_change << other
36
+ end
37
+ alias >> connects_to
38
+
39
+ def evaluate
40
+ return if @connected_by.nil?
41
+
42
+ @connected_by.current ? on : off
43
+ end
44
+
45
+ def to_s
46
+ current ? "1" : "0"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Logicuit
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
data/lib/logicuit.rb CHANGED
@@ -1,4 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "logicuit/version"
4
- require_relative "logicuit/and"
4
+ require_relative "logicuit/base"
5
+ require_relative "logicuit/gates/and"
6
+ require_relative "logicuit/gates/or"
7
+ require_relative "logicuit/gates/not"
8
+ require_relative "logicuit/signals/signal"
9
+ require_relative "logicuit/signals/clock"
10
+ require_relative "logicuit/circuits/sequential/d_flip_flop"
11
+ require_relative "logicuit/circuits/combinational/multiplexer2to1"
12
+ require_relative "logicuit/circuits/system_level/one_bit_cpu"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logicuit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koji NAKAMURA
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-02-05 00:00:00.000000000 Z
10
+ date: 2025-03-09 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: logi(c cir)cuit -> logicuit
13
13
  email:
@@ -21,7 +21,16 @@ files:
21
21
  - README.md
22
22
  - Rakefile
23
23
  - lib/logicuit.rb
24
- - lib/logicuit/and.rb
24
+ - lib/logicuit/base.rb
25
+ - lib/logicuit/circuits/combinational/half_adder.rb
26
+ - lib/logicuit/circuits/combinational/multiplexer2to1.rb
27
+ - lib/logicuit/circuits/sequential/d_flip_flop.rb
28
+ - lib/logicuit/circuits/system_level/one_bit_cpu.rb
29
+ - lib/logicuit/gates/and.rb
30
+ - lib/logicuit/gates/not.rb
31
+ - lib/logicuit/gates/or.rb
32
+ - lib/logicuit/signals/clock.rb
33
+ - lib/logicuit/signals/signal.rb
25
34
  - lib/logicuit/version.rb
26
35
  - sig/logicuit.rbs
27
36
  homepage: https://github.com/kozy4324/logicuit
@@ -46,7 +55,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
46
55
  - !ruby/object:Gem::Version
47
56
  version: '0'
48
57
  requirements: []
49
- rubygems_version: 3.6.2
58
+ rubygems_version: 3.6.5
50
59
  specification_version: 4
51
60
  summary: logi(c cir)cuit -> logicuit
52
61
  test_files: []
data/lib/logicuit/and.rb DELETED
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Logicuit
4
- # AND circuit
5
- class And
6
- def initialize(a, b) # rubocop:disable Naming/MethodParameterName
7
- @a = a
8
- @b = b
9
- end
10
-
11
- def signal
12
- [@a == 1 && @b == 1 ? 1 : 0]
13
- end
14
- end
15
- end